feat(missions): wire the workflow registry so phase config reaches the database

workflow_registry.rs had zero call sites -- lib.rs declared the module and
nothing ever called load() or get(). So templates/workflows/*.toml was never
read, and because the client's TEMPLATE_PRESETS carries only {kind, order_idx}
with no config, PhaseSpec.config defaulted to Value::Null and every
wizard-created mission stored a null mission_phases.config.

Every per-phase setting was therefore inert. `loop = "until_no_more_int_items"`
and `commit_policy = "on_green_tests"` described a scheduler that does not
exist AND had no path to the database. benchmark_runner and security_scan
already read phase_config(); they were reading from null.

- Mission create derives phases from the recipe when none are sent, and
  backfills config per phase (matched on kind+order_idx, then kind) when the
  caller sends shape without config. An explicit config always wins.
- phases_for_create takes Option<&WorkflowRecipe> rather than reaching for the
  global, because the registry resolves its directory relative to the process
  cwd -- which under cargo test is the crate root, not the repo root.
- GET /api/workflows serves the catalog; the wizard fetches it and falls back
  to TEMPLATE_PRESETS. Adding a TOML now adds a template with no FE change.
- load() runs at boot so a malformed recipe appears in the boot log instead of
  silently producing a mission with no phase config.

Also fixes a latent bug in all five recipes: `default_team_template` was
written below the first [[phases]] block, and TOML scopes a bare key after a
table header INTO that table -- so it parsed as
phases[last].config.default_team_template and the real field was always None.
Invisible while the registry was dead code. Moved above the phases, with a
test asserting it neither returns None nor leaks into a phase config.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-07-30 12:43:32 -07:00
co-authored by Claude Opus 5
parent d94487d3ba
commit 49bcf53b84
11 changed files with 327 additions and 29 deletions
+7
View File
@@ -288,6 +288,13 @@ async fn run() -> Result<(), String> {
// for INT-XX markers in event payloads and upserts mission_tasks
// rows so the canvas renders a live status timeline.
cm_api::task_card_worker::spawn(pool.clone());
// Load the workflow recipes now rather than lazily on first mission
// create, so a malformed TOML shows up in the boot log instead of
// silently yielding a mission with no phase config.
{
let recipes = cm_api::workflow_registry::load();
eprintln!("workflow_registry: {} recipe(s) available", recipes.len());
}
cm_api::phase_runner::spawn(pool.clone());
// Per-mission runtime container sweeper (C3): tears down mission
// runtime containers 30 min after the mission reaches a terminal