Skip to content

fix(bin): seal terminal runs after pipeline-owned head advances - #105

Merged
dnth merged 2 commits into
mainfrom
fm/fm-receipt-completion-headadvance-deadlock
Sep 5, 2026
Merged

dnth merged 2 commits into
mainfrom
fm/fm-receipt-completion-headadvance-deadlock

Conversation

@dnth

@dnth dnth commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Intent

Fix a deadlock in bin/fm-receipt-check.sh: --complete cannot record completion for a terminal PASSED no-mistakes run whose OWN pipeline (review/doc) commits advanced the branch head beyond the validated head.

The deadlock, all in bin/fm-receipt-check.sh:

  • Re-planning at the advanced head records the terminal run as validation_preplan_run_id, so --bind-run refuses it (pre-plan check).
  • --complete requires a bound run.
  • The pre-advance path also fails: a terminal (non-active) run cannot satisfy the head-drift exception because fm_nm_run_is_active requires an ACTIVE run.

Net effect: a genuinely green, fully-validated PR (all criteria receipts + CI green + a terminal PASSED no-mistakes run) cannot record its completion receipt without a wasteful fresh no-mistakes run on unchanged code. This hit live on port-3123a (PR #98) and on all four wave-2 ports (#100-#103); those had to be accepted on the green PR with the receipt seal skipped.

Required outcome: add a supported --complete path that seals a terminal PASSED no-mistakes run when the head advanced ONLY by that run's own pipeline (review/doc) commits, WITHOUT requiring a fresh run. Diagnose the exact deadlock first, then choose the minimal correct fix that recognizes pipeline-owned head advances as authoritative for the terminal run that produced them.

Safety invariant that MUST be preserved: --complete must STILL refuse to seal when the head advanced by commits that are NOT the run's own pipeline commits (genuine unvalidated drift), and must still require that the run genuinely passed.

Acceptance criteria:

  • AC1: --complete seals a terminal PASSED run whose branch head advanced ONLY by that run's own pipeline commits, without a fresh no-mistakes run. The exact deadlock scenario (terminal passed run + pipeline-advanced head, no active run) is reproduced in a colocated test and shown to complete.
  • AC2: The safety invariant is preserved and tested: --complete still refuses when the head advanced by foreign (non-pipeline) commits, and still requires the run genuinely passed. Colocated tests cover both the seal-succeeds and seal-refuses cases in the repo's existing fm-receipt-check test surface.

Decisions and tradeoffs made while implementing, which a reviewer reading only the diff would not know:

  • The chosen fix is deliberately minimal and lives at the existing head-drift exception rather than relaxing --bind-run's pre-plan guard. That guard is correct: after a re-plan at the advanced head, the terminal run genuinely predates the new plan. The real bug is that a re-plan was ever needed. With --complete now accepting the pipeline advance under the ORIGINAL plan generation, the re-plan trap is never entered, so the pre-plan guard was intentionally left untouched.

  • The advance is now authoritative in exactly two shapes. An ACTIVE run must still currently own the branch (branch_sync.state=pipeline_owned) - unchanged behavior. A TERMINAL run must have passed, and its own reported head is the authority for the commits it produced.

  • Requiring pipeline_owned for a terminal run is wrong by construction, not merely inconvenient: bin/fm-nm-run-lib.sh's own header documents that a terminal run has RELEASED the branch. So the old condition could never be satisfied by the exact case that needs sealing.

  • The foreign-drift refusal is carried by the PRE-EXISTING observed_head_full = current_head check immediately above the drift block, which was deliberately left in force for both shapes. A commit landed after the run finished is never reported as that run's head, so completion still refuses it. This is why no new drift detector was added: adding one would duplicate an invariant that already holds.

  • A new predicate fm_nm_run_is_terminal_passed was added to bin/fm-nm-run-lib.sh rather than inlined in fm-receipt-check.sh, because that lib is the documented single owner of no-mistakes run-attribution primitives shared by fm-crew-state.sh, fm-teardown.sh, and fm-receipt-check.sh. Its header comment explains why head equality remains the caller's obligation.

  • Test coverage is three cases inside one colocated test in the existing tests/fm-receipt-check.test.sh surface (no new runner): the deadlock now seals at the advanced head; foreign drift is refused (exit 2); and a terminal run that FAILED is refused (exit 2). The third case is not in the acceptance criteria as written but is required by the "still requires the run genuinely passed" half of AC2.

  • docs/verification/evidence-receipts.md is a maintainer-verification record, so its guarantee bullet was corrected and its recorded suite output refreshed from a real run. That recorded output was already two tests stale before this change; refreshing it was in scope because a verification record must state current behavior.

Verification actually run: tests/fm-receipt-check.test.sh 36 ok rc=0; tests/fm-crew-state.test.sh all passed (other consumer of the shared lib); bin/fm-lint.sh exit 0; bin/fm-doc-audience-check.sh ok.

Firstmate-Validation-Generation: b4977a6120f3ec41e7f72c1af0ef6c07

What Changed

  • Added terminal-passed run attribution so --complete accepts a branch head advanced solely by that run’s own review/doc pipeline commits.
  • Preserved refusal for foreign head drift and failed terminal runs, with colocated tests covering successful sealing and both refusal cases.
  • Updated the evidence-receipts verification record and refreshed its recorded test output.

Risk Assessment

✅ Low: The change is narrowly scoped, preserves exact-head and descendant safety checks, and adds behavioral coverage for terminal pipeline advances, foreign drift, and failed runs.

Testing

Ran the focused receipt-check suite covering seal success, foreign-drift refusal, and failed-run refusal, plus the shared fm-crew-state consumer suite; all passed, with a reviewer-visible transcript saved in the evidence directory.

Evidence: Receipt-check targeted test transcript
ok - fm-receipt-check help renders an executable generation-bound bind command
ok - fm-receipt-check reports required, evidenced, and missing ids deterministically
ok - fm-receipt-check distinguishes complete evidence from invalid JSONL
ok - structured success and negative outcomes control criterion evidence
ok - pinned brief and metadata delivery modes must match exactly
ok - pinned metadata owner rejects hard-linked validation records
ok - invalid ship briefs fail and scout/report behavior stays unchanged
ok - early snapshot failures release cleanup without a FIFO reader
ok - snapshot readiness publication failures terminate without waiting
ok - fm-receipt-check pins task evidence and rejects hard-linked ledgers
ok - receipt append and check consume one criterion grammar
ok - exact bound runs complete from the shared current CI-log readiness predicate
ok - finding-to-criterion invalidations remain inspectable in task metadata
ok - run binding resolves abbreviated heads and rejects non-planned commits
ok - binding and completion work against the real agent-supplied intent-log shape while wrong runs fail closed
ok - completion accepts only active pipeline-owned descendant heads
ok - terminal passed runs seal their own pipeline advance and refuse foreign drift
ok - low-risk mechanical changes can skip a full No-Mistakes run
ok - low risk requires safe changelog prose and file-bound mechanical evidence
ok - implementation completion refreshes per head and remains idempotent
ok - plan publication holds the pinned ledger boundary against concurrent receipts
ok - diff summary errors fail closed before risk classification
ok - successful terminal runs bind while failed runs remain rejected
ok - No-Mistakes status and CI-log observations are bounded
ok - authoritative documentation remains high
ok - terminal delivery paths record one completion timestamp at their boundary
ok - completion signals release the validation lock for retry
ok - replanning invalidates prior run and completion bindings
ok - dirty worktrees cannot be planned or completed
ok - git status errors fail implementation, planning, and completion cleanliness gates
ok - shared cleanliness inspects ignored submodules
ok - direct and local plans never invoke No-Mistakes
ok - local completion requires fast-forward readiness
ok - local readiness and landing share one fail-closed default resolver
ok - security and uncertain changes retain full No-Mistakes validation
ok - direct-PR and local-only retain evidence gates without invoking No-Mistakes

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • tests/fm-receipt-check.test.sh
  • tests/fm-crew-state.test.sh
  • Captured receipt-check output to ~/.no-mistakes/evidence/01M1QMJV8HRP0R09MRTCY4WGP6/fm-receipt-check.test.log
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

dnth added 2 commits September 5, 2026 09:49
…ead advance

`--complete` refused a genuinely validated PR whenever the no-mistakes run's
own review and doc commits advanced the branch head past the validated head
and the run then reached a terminal PASSED state. The head-drift exception
required `fm_nm_run_is_active` plus `branch_sync.state=pipeline_owned`, both
of which a terminal run cannot satisfy: it has released the branch by
construction. Replanning at the advanced head did not help either, because
the plan records that terminal run as `validation_preplan_run_id` and
`--bind-run` then refuses it, while `--complete` requires a bound run. The
only escape was a fresh no-mistakes run over unchanged code.

The advance is now authoritative in two shapes. An active run must still
currently own the branch. A terminal run must have passed, and its own
reported head is the authority for the commits it produced. The existing
`observed_head_full = current_head` check is what keeps that honest: a
foreign commit landed after the run finished is never reported as the run's
head, so it still fails completion, and a terminal run that did not pass
still seals nothing.

Claude-Session: https://claude.ai/code/session_018ALZVEheSodHtcmw2cuzG4
@dnth
dnth merged commit 671ee08 into main Sep 5, 2026
28 of 29 checks passed
@dnth
dnth deleted the fm/fm-receipt-completion-headadvance-deadlock branch September 5, 2026 07:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant