diff --git a/NEWS.md b/NEWS.md index 1438d76..52f88c8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -39,6 +39,10 @@ First development changelog. dsprrr is experimental; the API may change. `refine(best_of_n(mod))`) compose their ids cleanly instead of crashing with a duplicate-argument error (#dsprrr-pcd, #dsprrr-wx6). +* `pin_module_config()` now errors clearly for pipelines and other unsupported + module types instead of silently serialising them as an empty `PredictModule` + and dropping every step on restore (#dsprrr-07u). + ## Internal * Tests now isolate the on-disk cache to a temporary directory, so running diff --git a/R/orchestration.R b/R/orchestration.R index 62bf271..8216816 100644 --- a/R/orchestration.R +++ b/R/orchestration.R @@ -159,6 +159,15 @@ restore_module_config <- function(config, signature = NULL) { } serialize_module_config_v2 <- function(module) { + if (inherits(module, "PipelineModule")) { + cli::cli_abort(c( + "Pipeline persistence is not yet supported", + "x" = "{.fn pin_module_config} cannot serialise a {.cls PipelineModule}", + "i" = "Persisting it here would silently drop every step and its demos", + "i" = "Pin each step module individually, or rebuild the pipeline from source" + )) + } + kind <- module_kind(module) supported <- c("predict", "react", "chain_of_thought", "multichain") if (!kind %in% supported) { diff --git a/R/run.R b/R/run.R index c588dfa..17ed9be 100644 --- a/R/run.R +++ b/R/run.R @@ -292,7 +292,11 @@ module_kind <- function(module) { "ReactModule" = "react", "MultiChainComparisonModule" = "multichain", "PredictModule" = "predict", - "predict" + # Fall back to the actual class name rather than silently claiming + # "predict" -- otherwise unsupported module types (pipelines, ensembles, + # RAG, ...) pass the persistence allow-list and serialize as a bare + # PredictModule, losing all of their structure. + class(module)[1] ) } diff --git a/tests/testthat/test-orchestration.R b/tests/testthat/test-orchestration.R index 145e3a5..e7acd2d 100644 --- a/tests/testthat/test-orchestration.R +++ b/tests/testthat/test-orchestration.R @@ -465,3 +465,20 @@ test_that("module config round-trips correctly", { expect_true(restored$is_compiled()) expect_equal(restored$state$best_score, 0.92) }) + +test_that("pin_module_config refuses to silently destroy pipelines (dsprrr-07u)", { + skip_if_not_installed("pins") + + m1 <- module(signature("question -> thought"), type = "predict") + m2 <- module(signature("thought -> answer"), type = "predict") + pipe <- pipeline(m1, m2) + board <- pins::board_temp() + + # Regression: module_kind() collapsed unknown classes to "predict", so a + # pipeline pinned without error and restored as a single empty PredictModule, + # silently losing every step and its bootstrapped demos. + expect_error( + pin_module_config(board, "pipe", pipe), + "[Pp]ipeline" + ) +})