From 1a50c821f89f2938a22b1f2a88fefdd3be9d1c41 Mon Sep 17 00:00:00 2001 From: dnth Date: Sat, 5 Sep 2026 09:49:44 +0800 Subject: [PATCH 1/2] fix(receipt-check): seal completion for a terminal passed run's own head 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 --- bin/fm-nm-run-lib.sh | 19 ++++++ bin/fm-receipt-check.sh | 29 ++++++--- docs/verification/evidence-receipts.md | 13 ++-- tests/fm-receipt-check.test.sh | 87 ++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 12 deletions(-) diff --git a/bin/fm-nm-run-lib.sh b/bin/fm-nm-run-lib.sh index 632b30022bf..8241d093947 100644 --- a/bin/fm-nm-run-lib.sh +++ b/bin/fm-nm-run-lib.sh @@ -160,6 +160,25 @@ fm_nm_run_is_pipeline_owned_active() { # fm_nm_run_is_active "$1" } +# 0 when captured `axi status` shows a run that reached a terminal PASSED state. +# A terminal run has released the branch, so branch_sync no longer reports +# pipeline_owned and fm_nm_run_is_pipeline_owned_active above correctly rejects +# it. Its OWN reported head is then the authority for the commits that run +# produced, including the review and doc commits its pipeline landed after the +# validated head. Callers must therefore still require that reported head to be +# the current worktree head: that is exactly what refuses foreign commits landed +# after the run finished, which the run never reports as its head. +fm_nm_run_is_terminal_passed() { # + local status outcome + if fm_nm_run_is_active "$1"; then return 1; fi + status=$(fm_nm_field "$1" status) + outcome=$(fm_nm_field "$1" outcome) + case "$outcome:$status" in + passed:*|checks-passed:*|*:passed|*:checks-passed) return 0 ;; + esac + return 1 +} + # During no-mistakes' ci monitor, top-level status and outcome stay running after # checks turn green until the PR merges, while the append-only ci log records the # transition. The most recent recognized log marker is therefore authoritative: diff --git a/bin/fm-receipt-check.sh b/bin/fm-receipt-check.sh index 84fdb2bb70e..5998e0a423b 100755 --- a/bin/fm-receipt-check.sh +++ b/bin/fm-receipt-check.sh @@ -44,8 +44,11 @@ # The resolved validation_tier, validation_path, reason code, base, head, size, # and start time are appended to state/.meta for durable inspection. # Every completion records validation_completed_head and refuses current head -# drift unless the bound active No-Mistakes run proves a pipeline-owned -# descendant of the latest validation_head. +# drift unless the bound No-Mistakes run proves a descendant of the latest +# validation_head: an active run must currently own the branch, while a terminal +# PASSED run proves the advance through its own reported head. A terminal run +# therefore needs no replan and no fresh run to seal its own pipeline commits, +# and foreign commits landed after the run still refuse completion. # When --plan returns path=receipts-mechanical, append fresh successful mechanical # evidence for every changed file with: # @@ -808,11 +811,23 @@ record_validation_completed() { echo "error: pipeline run branch is not the current worktree branch" >&2 return 1 fi - fm_nm_run_is_active "$run_out" \ - || { release_validation_lock; echo "error: current head advanced without an active bound pipeline run" >&2; return 1; } - branch_sync_state=$(fm_nm_branch_sync_state "$run_out") - [ "$branch_sync_state" = pipeline_owned ] \ - || { release_validation_lock; echo "error: current head advance lacks authoritative pipeline ownership" >&2; return 1; } + # The advance is authoritative in exactly two shapes, and the head + # equality checked above is what keeps both honest. While the run is + # ACTIVE the pipeline must currently own the branch. Once the run is + # TERMINAL it has released the branch, so pipeline ownership is gone by + # construction and requiring it would refuse a genuinely passed run + # whose own review and doc commits advanced the head; there the run's + # own reported head is the authority, and a foreign commit landed after + # the run finished still fails the head-equality check because the run + # never reports it. + if fm_nm_run_is_active "$run_out"; then + branch_sync_state=$(fm_nm_branch_sync_state "$run_out") + [ "$branch_sync_state" = pipeline_owned ] \ + || { release_validation_lock; echo "error: current head advance lacks authoritative pipeline ownership" >&2; return 1; } + else + fm_nm_run_is_terminal_passed "$run_out" \ + || { release_validation_lock; echo "error: current head advanced without a passing bound pipeline run" >&2; return 1; } + fi completion_head=$current_head fi observed=bound-matching-no-mistakes-run diff --git a/docs/verification/evidence-receipts.md b/docs/verification/evidence-receipts.md index 670511b7875..3a045199b93 100644 --- a/docs/verification/evidence-receipts.md +++ b/docs/verification/evidence-receipts.md @@ -1,6 +1,6 @@ # Evidence receipts and risk routing verification -This record captures the active maintainer evidence for ship-task acceptance receipts and conservative validation routing as of 2026-08-26. +This record captures the active maintainer evidence for ship-task acceptance receipts and conservative validation routing as of 2026-09-05. The exact receipt key and type schema is owned by the header and `--help` output of `bin/fm-receipt-schema.sh`; the criterion parser, classifier thresholds, metadata fields, and lifecycle commands are owned by the headers and help output of `bin/fm-receipt-check.sh`, `bin/fm-receipt.sh`, and `bin/fm-receipt-store.sh` at their respective executable boundaries. ## Guarantees under test @@ -39,7 +39,8 @@ The exact receipt key and type schema is owned by the header and `--help` output - Successful exact-head runs can bind after reaching checks-passed or passed, while failed and cancelled runs remain ineligible. - No-Mistakes status, intent, and CI-log observations use the shared bounded call boundary. - Every completion requires path-specific terminal evidence and records its plan path and authoritative completed head. -- A changed worktree head invalidates completion unless the bound active No-Mistakes run proves a pipeline-owned descendant of the planned head; unrelated, missing, or ambiguous drift remains refused. +- A changed worktree head invalidates completion unless the bound No-Mistakes run proves a descendant of the planned head: an active run must currently own the branch, while a terminal passed run proves the advance through its own reported head. +- Unrelated, missing, or ambiguous drift remains refused, and a terminal run that did not pass never seals an advance. - Local-only readiness and guarded landing consume one fail-closed executable default-branch resolver. - Planning and completion refuse tracked, staged, or untracked worktree changes. - Initial planning accepts a caller base only when it equals the repository's authoritative merge boundary, so a later ancestor cannot hide earlier task commits. @@ -90,19 +91,21 @@ 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, intent, and CI-log observations are bounded +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 - intent binding accepts one exact record and resolves abbreviated heads -ok - completion accepts only active pipeline-owned descendant heads 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 diff --git a/tests/fm-receipt-check.test.sh b/tests/fm-receipt-check.test.sh index 7ea735e60b5..ee9e02b47f6 100755 --- a/tests/fm-receipt-check.test.sh +++ b/tests/fm-receipt-check.test.sh @@ -925,6 +925,92 @@ test_completion_accepts_only_pipeline_owned_head_advance() { pass "completion accepts only active pipeline-owned descendant heads" } +test_terminal_passed_run_seals_its_own_pipeline_head_advance() { + local id base project initial_head current_head generation status out rc + + # The deadlock: the run's own review/doc commits advanced the branch head past + # the validated head and the run then reached a terminal PASSED state, so no + # active pipeline-owned run remains to prove the advance. + id=receipt-terminal-advance + base=$(make_project "$id" no-mistakes localized) + add_receipt "$id" AC1 test "2 passed" + add_receipt "$id" AC2 lint passed + FM_FAKE_NM_STATUS='' FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --plan --base "$base" >/dev/null || fail "terminal advance plan failed" + project="$TMP_ROOT/project-$id" + initial_head=$(git -C "$project" rev-parse HEAD) + generation=$(grep '^validation_generation=' "$HOME_DIR/state/$id.meta" | tail -1 | cut -d= -f2-) + status=$(nm_status RUN-terminal-advance "$initial_head" pending) + FM_FAKE_NM_STATUS="$status" FM_FAKE_NM_INTENT="Firstmate-Validation-Generation: $generation" \ + FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --bind-run RUN-terminal-advance --generation "$generation" >/dev/null \ + || fail "terminal advance run binding failed" + printf 'doc commit\n' >> "$project/src/app.sh" + git -C "$project" add src/app.sh + git -C "$project" commit -q -m 'no-mistakes: docs' + current_head=$(git -C "$project" rev-parse HEAD) + status=$(nm_pipeline_status RUN-terminal-advance "fm/$id" "$current_head" completed passed agent_owned) + out=$(FM_FAKE_NM_STATUS="$status" \ + FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --complete --terminal-evidence no-mistakes-passed) \ + || fail "terminal passed run could not seal its own pipeline head advance" + printf '%s' "$out" | jq -e --arg head "$current_head" '.status == "completed" and .completed_head == $head' >/dev/null \ + || fail "terminal passed completion did not bind the advanced head" + + # Foreign drift: the terminal run still reports the validated head, so the + # commit that advanced the branch is not its own and must not be sealed. + id=receipt-terminal-foreign-drift + base=$(make_project "$id" no-mistakes localized) + add_receipt "$id" AC1 test "2 passed" + add_receipt "$id" AC2 lint passed + FM_FAKE_NM_STATUS='' FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --plan --base "$base" >/dev/null || fail "foreign drift plan failed" + project="$TMP_ROOT/project-$id" + initial_head=$(git -C "$project" rev-parse HEAD) + generation=$(grep '^validation_generation=' "$HOME_DIR/state/$id.meta" | tail -1 | cut -d= -f2-) + status=$(nm_status RUN-foreign-drift "$initial_head" pending) + FM_FAKE_NM_STATUS="$status" FM_FAKE_NM_INTENT="Firstmate-Validation-Generation: $generation" \ + FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --bind-run RUN-foreign-drift --generation "$generation" >/dev/null \ + || fail "foreign drift run binding failed" + printf 'hand edit\n' >> "$project/src/app.sh" + git -C "$project" add src/app.sh + git -C "$project" commit -q -m 'unvalidated hand edit' + status=$(nm_pipeline_status RUN-foreign-drift "fm/$id" "$initial_head" completed passed agent_owned) + FM_FAKE_NM_STATUS="$status" \ + FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --complete --terminal-evidence no-mistakes-passed >/dev/null 2>&1 + rc=$? + expect_code 2 "$rc" "terminal completion sealed foreign unvalidated drift" + + # A terminal run that did not pass never seals an advance it produced. + id=receipt-terminal-failed-advance + base=$(make_project "$id" no-mistakes localized) + add_receipt "$id" AC1 test "2 passed" + add_receipt "$id" AC2 lint passed + FM_FAKE_NM_STATUS='' FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --plan --base "$base" >/dev/null || fail "failed terminal advance plan failed" + project="$TMP_ROOT/project-$id" + initial_head=$(git -C "$project" rev-parse HEAD) + generation=$(grep '^validation_generation=' "$HOME_DIR/state/$id.meta" | tail -1 | cut -d= -f2-) + status=$(nm_status RUN-failed-advance "$initial_head" pending) + FM_FAKE_NM_STATUS="$status" FM_FAKE_NM_INTENT="Firstmate-Validation-Generation: $generation" \ + FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --bind-run RUN-failed-advance --generation "$generation" >/dev/null \ + || fail "failed terminal advance run binding failed" + printf 'partial fix\n' >> "$project/src/app.sh" + git -C "$project" add src/app.sh + git -C "$project" commit -q -m 'no-mistakes: partial fix' + current_head=$(git -C "$project" rev-parse HEAD) + status=$(nm_pipeline_status RUN-failed-advance "fm/$id" "$current_head" failed failed agent_owned) + FM_FAKE_NM_STATUS="$status" \ + FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --complete --terminal-evidence no-mistakes-passed >/dev/null 2>&1 + rc=$? + expect_code 2 "$rc" "terminal completion sealed a run that did not pass" + pass "terminal passed runs seal their own pipeline advance and refuse foreign drift" +} + test_no_mistakes_observations_are_bounded() { local hang_nm id base project head generation running ci_status rc hang_nm="$TMP_ROOT/hang-no-mistakes" @@ -1476,6 +1562,7 @@ test_claim_invalidation_marker_is_append_only_and_idempotent test_run_heads_resolve_authoritatively test_agent_supplied_intent_log_binds_and_completes test_completion_accepts_only_pipeline_owned_head_advance +test_terminal_passed_run_seals_its_own_pipeline_head_advance test_low_risk_skips_no_mistakes_under_explicit_policy test_low_risk_requires_safe_prose_and_applicable_evidence test_implementation_completion_precedes_planning From fa4fcac69c6145db30da8084709487bdbb952b01 Mon Sep 17 00:00:00 2001 From: dnth Date: Sat, 5 Sep 2026 10:08:10 +0800 Subject: [PATCH 2/2] no-mistakes(document): Refresh receipt verification documentation --- docs/verification/evidence-receipts.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/verification/evidence-receipts.md b/docs/verification/evidence-receipts.md index 3a045199b93..462eef5e54e 100644 --- a/docs/verification/evidence-receipts.md +++ b/docs/verification/evidence-receipts.md @@ -54,7 +54,7 @@ The exact receipt key and type schema is owned by the header and `--help` output ## Verification environment -- Date: 2026-08-26. +- Date: 2026-09-05. - ShellCheck: 0.11.0. - Git: 2.34.1. @@ -93,7 +93,6 @@ ok - exact bound runs complete from the shared current CI-log readiness predicat 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