The last open item in the silent-success class: a coding phase that
changed no files reported `completed` — the same status a phase gets for
delivering tested, reviewed, pushed work. Mission `019fcf62` completed
that way with its agents silently unpinned from the repo, and nothing in
the platform disagreed; it was found by a script diffing the forge.
The verdict is applied at capture rather than at completion, because
capture selects on `status = 'completed'` — the platform does not know
whether a phase produced anything until after it has already finished.
Three conditions must hold before failing a phase, because a false
positive here fails honest work: the phase is a coding phase (research
phases legitimately write nothing to the tree), the diff was actually
computed (an uncomputable diff also reports zero files — blaming the
agent for a platform fault is the same defect wearing different
clothes), and `allow_empty` is not set. Only an explicit `true` opts
out, so a typo leaves the check armed. Registered in phase_config with
its reader named, per the seam-2 rule.
Also closes an ordering hazard this exposed: capture is batched and runs
after a phase completes, so a backlogged mission could close as
'completed' and only then have capture discover an empty phase — leaving
a 'completed' mission holding a 'failed' phase, unfixable because the
mission-close CASE only touches 'running' rows. A repo-bearing mission
now waits for its work to be captured before closing.
Adds a `noop` scenario to the harness: a phase told to change nothing,
which PASSES only when the phase comes back `failed`. Same discipline as
the uid self-test — a check that has never been seen to fire has not
been shown to work.
Co-Authored-By: Claude Opus 5 <[email protected]>
A verification run against the deployed stack found a chain mission whose
phase 0 reported `completed` with zero files, no commit error and no push
error — indistinguishable from a phase that correctly had nothing to do.
Three separate defects had to line up, each of them the same shape: a
failure sharing its representation with a legitimate negative result.
1. `pin_agent_workspaces` embedded the whole config in one `sh -c` argv.
That works until the file grows — config gains a block per provisioned
claw — then fails with `argument list too long`. Now written through
the tar upload API, which has no argv limit, so the failure mode is
gone rather than merely further away.
2. A failed pin was logged "(continuing)". Without the pin, agents write
to their sandboxes and the committer finds nothing in /mission/repo —
the mission cannot deliver, so the launch now fails where someone is
still looking. The restart that applies the pin is fatal for the same
reason.
3. `capture_phase_diff_at` swallowed `git diff` failures with
`unwrap_or_default`, so an unreadable base landed `empty: true,
files_changed: 0` — byte-identical to an honest no-op. The error is now
recorded as `diff_error`, and an empty patch that came from a failed
diff is no longer trusted to mean an unchanged tree.
Adds scripts/verify-mission-delivery.sh, which found #1 and #2 on its
first real run. Its probes are fail-closed: no placeholder values, a
self-test that proves the uid probe can detect the split it looks for,
and FAIL-NORUN for a scenario that never executed. Its own first version
had this bug too — a `die` inside `$(...)` exited the subshell, so a run
that could not authenticate printed "all checks passed" and exited 0.
Co-Authored-By: Claude Opus 5 <[email protected]>