fix(missions): close the three seams behind this run of failures
ci / gates (push) Failing after 6s
ci / rust (push) Skipped
ci / frontend (push) Skipped
ci / e2e (push) Skipped
ci / publish (push) Skipped

Seam 1 — delivery inferred checkout state from the tree, so whether work
survived depended on what the agent happened to do. 019fc444 committed
and left a clean tree; 019fc476 had its base advanced to match HEAD;
019fc450 survived only because a phase FAILED to commit and left the tree
dirty. Same code, opposite outcomes, decided by the agent.

mark_phase_started records the fact at phase launch, before the agent
acts, so every one of those states answers identically. The tree checks
remain as a second line of defence for pre-existing checkouts.

Seam 2 — phase config was accepted, stored and read by nobody. That was
`task`: every phase of every mission got identical instructions. The new
phase_config registry names the reader for each live key and lists the
eight that are declared-but-unimplemented, reporting both at mission
creation so an author sees what will not happen. Its CI test found one I
had missed: security_hardening.toml sets phase-level mcp_bundles asking
for gitea_forge + security_scan, but bundles come from the TEAM template
and the phase gets neither.

Seam 4 — push_url_for collapsed a failed query, an unbound repo and a
missing clone_url into one None, so a database fault was recorded as
"nothing to push to" and metadata read `pushed: null, push_error: null` —
the same ambiguity commit_error already fixed. Each case now carries its
reason into the artifact, and a local git failure during publish is
recorded rather than dropped by .ok().

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-02 18:58:45 -07:00
co-authored by Claude Opus 5
parent bb34ef1b7e
commit ec85f6c8da
6 changed files with 415 additions and 19 deletions
+7 -1
View File
@@ -196,10 +196,16 @@ fn phases_for_create(
})
.map(|rp| rp.config.clone())
.unwrap_or(Value::Null);
let config = merge_config(base, p.config);
// Say what this phase asked for that will not happen. A config key
// nothing reads is silent by construction — `task` sat unread
// through every mission until two phases with different tasks
// produced identical output.
crate::phase_config::report(&p.kind, p.order_idx, &config);
NewMissionPhase {
kind: p.kind,
order_idx: p.order_idx,
config: merge_config(base, p.config),
config,
}
})
.collect()