From 21efb729c3101e5cd56d11cdd55c1b8cdff93d85 Mon Sep 17 00:00:00 2001 From: dnth Date: Fri, 11 Sep 2026 01:45:03 +0800 Subject: [PATCH 1/6] fix(bin): accept same-run pipeline-owned descendants in validation binding and completion Add fm_nm_head_is_accounted to bin/fm-nm-run-lib.sh to recognize planned-head, faithful-restamp, and run-owned-descendant chains, including a restamped chain followed by additional pipeline commits. Update bin/fm-receipt-check.sh so --bind-run and --complete accept a same-run descendant only when provenance checks pass: the run reports the correct task branch, and the run is either active with pipeline ownership (branch_sync pipeline_owned/synchronized, with an axi sync --check fallback when axi status omits branch_sync) or terminal passed. Cross-check the run's submitted_head and current_head against the planned and run heads when axi sync reports them. Add focused behavioral tests covering active/terminal pipeline-owned descendant binding and completion, restamp-plus-doc chains, axi sync fallback, and negative controls for unowned descendants, wrong branch, foreign drift, and wrong generation. Update docs/verification/evidence-receipts.md with the expanded descendant-advance guarantee, the new predicate, and the current verification output. Generated with Devin Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- bin/fm-nm-run-lib.sh | 31 ++++- bin/fm-receipt-check.sh | 107 ++++++++++++++--- docs/verification/evidence-receipts.md | 34 ++++-- tests/fm-receipt-check.test.sh | 157 ++++++++++++++++++++++++- 4 files changed, 295 insertions(+), 34 deletions(-) diff --git a/bin/fm-nm-run-lib.sh b/bin/fm-nm-run-lib.sh index d6d552cb71c..ce00ffbe47f 100644 --- a/bin/fm-nm-run-lib.sh +++ b/bin/fm-nm-run-lib.sh @@ -121,6 +121,32 @@ fm_nm_head_is_faithful_restamp() { # + local wt=$1 base=$2 validated=$3 candidate=$4 + local base_full validated_full candidate_full validated_count prefix_head prefix_count + base_full=$(fm_nm_resolve_head "$wt" "$base") || return 1 + validated_full=$(fm_nm_resolve_head "$wt" "$validated") || return 1 + candidate_full=$(fm_nm_resolve_head "$wt" "$candidate") || return 1 + [ "$candidate_full" = "$validated_full" ] && return 0 + fm_nm_head_is_faithful_restamp "$wt" "$base_full" "$validated_full" "$candidate_full" && return 0 + fm_nm_head_descends_from "$wt" "$validated_full" "$candidate_full" && return 0 + # Pipeline restamps can be followed by additional owned commits; the leading + # segment must be a faithful restamp of the validated chain from the base. + validated_count=$(git -C "$wt" rev-list --count "$base_full..$validated_full" 2>/dev/null) || return 1 + [ "$validated_count" -gt 0 ] || return 1 + prefix_head=$(git -C "$wt" rev-list --first-parent --reverse "$base_full..$candidate_full" 2>/dev/null | head -n "$validated_count" | tail -1) || return 1 + [ -n "$prefix_head" ] || return 1 + prefix_count=$(git -C "$wt" rev-list --count "$base_full..$prefix_head" 2>/dev/null) || return 1 + [ "$prefix_count" -eq "$validated_count" ] || return 1 + fm_nm_head_is_faithful_restamp "$wt" "$base_full" "$validated_full" "$prefix_head" || return 1 + git -C "$wt" merge-base --is-ancestor "$prefix_head" "$candidate_full" 2>/dev/null || return 1 + [ "$prefix_head" != "$candidate_full" ] || return 1 +} + # 0 when a run's branch presentation identifies the checked-out branch. The # no-mistakes CLI renders Firstmate's slash branch names with a hyphen, so both # authoritative spellings are accepted and no other branch is normalized. @@ -180,7 +206,10 @@ fm_nm_head_resolvable() { # # is absent (no run on the current branch, another branch's run, or a CLI # without branch sync). fm_nm_run_is_pipeline_owned_active() { # - [ "$(fm_nm_branch_sync_state "$1")" = pipeline_owned ] || return 1 + case "$(fm_nm_branch_sync_state "$1")" in + pipeline_owned|synchronized) ;; + *) return 1 ;; + esac fm_nm_run_is_active "$1" } diff --git a/bin/fm-receipt-check.sh b/bin/fm-receipt-check.sh index d6b2f8894ce..16e012bd0fe 100755 --- a/bin/fm-receipt-check.sh +++ b/bin/fm-receipt-check.sh @@ -674,15 +674,63 @@ if [ "$ACTION" = bind-run ]; then passed:*|checks-passed:*|*:passed|*:checks-passed) BIND_STATE_OK=1 ;; running:*|fixing:*|ci:*|awaiting_approval:*) BIND_STATE_OK=1 ;; esac - # The run's head is the planned commit itself or a faithful restamp of its - # validated chain, proven from the recorded validation base. + # The run's head is the planned commit itself, a faithful restamp of the + # validated chain, or a proven pipeline-owned descendant that advanced after + # the plan was recorded (review/doc/lint fix commits). Allow descendants so + # binding does not require a fresh plan for every no-mistakes fix round. BIND_RUN_HEAD=$(fm_nm_resolve_head "$BIND_WORKTREE" "$BIND_OBSERVED_HEAD" || true) - if [ -n "$BIND_RUN_HEAD" ] && [ "$BIND_RUN_HEAD" != "$BIND_HEAD" ] \ - && ! fm_nm_head_is_faithful_restamp "$BIND_WORKTREE" "$BIND_BASE" "$BIND_HEAD" "$BIND_RUN_HEAD"; then - BIND_RUN_HEAD= + BIND_HEAD_ACCOUNTED=0 + if [ -n "$BIND_RUN_HEAD" ]; then + if [ "$BIND_RUN_HEAD" = "$BIND_HEAD" ]; then + BIND_HEAD_ACCOUNTED=1 + elif fm_nm_head_is_faithful_restamp "$BIND_WORKTREE" "$BIND_BASE" "$BIND_HEAD" "$BIND_RUN_HEAD"; then + BIND_HEAD_ACCOUNTED=1 + elif fm_nm_head_is_accounted "$BIND_WORKTREE" "$BIND_BASE" "$BIND_HEAD" "$BIND_RUN_HEAD"; then + # The head advanced after the plan; require branch identity and active or + # terminal passed ownership so an unrelated descendant cannot bind. + run_branch=$(fm_nm_field "$BIND_OUT" branch) + if [ -n "$run_branch" ] && fm_nm_branch_matches_worktree "$BIND_WORKTREE" "$run_branch"; then + if fm_nm_run_is_terminal_passed "$BIND_OUT"; then + BIND_HEAD_ACCOUNTED=1 + elif fm_nm_run_is_active "$BIND_OUT"; then + branch_sync_state=$(fm_nm_branch_sync_state "$BIND_OUT") + if [ "$branch_sync_state" != pipeline_owned ] && [ "$branch_sync_state" != synchronized ]; then + # Real no-mistakes `axi status` for an active run does not include a + # branch_sync block; use `axi sync --check` to confirm pipeline ownership. + SYNC_OUT=$(fm_nm_run_checked "$BIND_WORKTREE" "$NM_TIMEOUT" axi sync --check) || SYNC_OUT= + if [ -n "$SYNC_OUT" ]; then + sync_state=$(fm_nm_branch_sync_state "$SYNC_OUT") + sync_run=$(fm_nm_field "$SYNC_OUT" run) + if [ "$sync_state" = pipeline_owned ] || [ "$sync_state" = synchronized ]; then + if [ -n "$sync_run" ] && [ "$sync_run" = "$RUN_ID_INPUT" ]; then + branch_sync_state=$sync_state + # Cross-check the run's own submitted and current heads when axi + # sync reports them; this is the authoritative run-owned evidence. + sync_submitted=$(fm_nm_field "$SYNC_OUT" submitted_head) + sync_current=$(fm_nm_field "$SYNC_OUT" current_head) + if [ -n "$sync_submitted" ]; then + if [ "$(fm_nm_resolve_head "$BIND_WORKTREE" "$sync_submitted" || true)" != "$BIND_HEAD" ]; then + branch_sync_state= + fi + fi + if [ -n "$sync_current" ]; then + if [ "$(fm_nm_resolve_head "$BIND_WORKTREE" "$sync_current" || true)" != "$BIND_RUN_HEAD" ]; then + branch_sync_state= + fi + fi + fi + fi + fi + fi + if [ "$branch_sync_state" = pipeline_owned ] || [ "$branch_sync_state" = synchronized ]; then + BIND_HEAD_ACCOUNTED=1 + fi + fi + fi + fi fi [ "$BIND_OBSERVED_ID" = "$RUN_ID_INPUT" ] \ - && [ -n "$BIND_RUN_HEAD" ] \ + && [ "$BIND_HEAD_ACCOUNTED" -eq 1 ] \ && [ "$BIND_STATE_OK" -eq 1 ] \ || { echo "error: No-Mistakes run does not match the latest plan" >&2; exit 2; } [ -n "$BIND_GENERATION" ] || { echo "error: validation generation is missing" >&2; exit 2; } @@ -729,7 +777,7 @@ if [ "$ACTION" = mechanical-ready ]; then fi record_validation_completed() { - local started path generation published_generation completed completed_head completed_path completed_evidence completed_generation now worktree validation_base validated_head current_head completion_head expected_evidence observed pr pr_head branch boundary new_receipts run_id run_path run_generation run_out observed_id observed_head observed_head_full outcome run_status default_ref default_branch ci_state run_ready changed_file completion_files run_branch current_branch branch_sync_state run_head_matches_current restamp_accounted + local started path generation published_generation completed completed_head completed_path completed_evidence completed_generation now worktree validation_base validated_head current_head completion_head expected_evidence observed pr pr_head branch boundary new_receipts run_id run_path run_generation run_out observed_id observed_head observed_head_full outcome run_status default_ref default_branch ci_state run_ready changed_file completion_files run_branch current_branch branch_sync_state run_head_matches_current restamp_accounted SYNC_OUT sync_state sync_run sync_submitted sync_current VALIDATION_LOCK="$STATE/.$ID.validation-plan.lock" if ! mkdir "$VALIDATION_LOCK" 2>/dev/null; then VALIDATION_LOCK= @@ -835,8 +883,7 @@ record_validation_completed() { [ "$run_head_matches_current" -eq 1 ] \ || { release_validation_lock; echo "error: bound No-Mistakes run head does not account for the current worktree content" >&2; return 1; } if [ "$current_head" != "$validated_head" ]; then - fm_nm_head_descends_from "$worktree" "$validated_head" "$current_head" \ - || fm_nm_head_is_faithful_restamp "$worktree" "$validation_base" "$validated_head" "$current_head" \ + fm_nm_head_is_accounted "$worktree" "$validation_base" "$validated_head" "$current_head" \ || { release_validation_lock; echo "error: current head neither descends from nor reproduces the implementation head" >&2; return 1; } completion_head=$current_head fi @@ -848,18 +895,40 @@ record_validation_completed() { echo "error: pipeline run branch is not the current worktree branch" >&2 return 1 fi - # The advance is authoritative in exactly two shapes, and the head - # accounting 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 that accounting because the run reports - # neither that commit nor its content. + # The advance is authoritative only while the run is ACTIVE and the + # pipeline owns the branch, or once the run has reached a terminal PASSED + # state and released the branch. Active ownership is shown by a + # branch_sync state of pipeline_owned or synchronized, either directly in + # the axi status output or in `axi sync --check` for current no-mistakes. if fm_nm_run_is_active "$run_out"; then branch_sync_state=$(fm_nm_branch_sync_state "$run_out") - if [ "$branch_sync_state" != pipeline_owned ]; then + if [ "$branch_sync_state" != pipeline_owned ] && [ "$branch_sync_state" != synchronized ]; then + SYNC_OUT=$(fm_nm_run_checked "$worktree" "$NM_TIMEOUT" axi sync --check) || SYNC_OUT= + if [ -n "$SYNC_OUT" ]; then + sync_state=$(fm_nm_branch_sync_state "$SYNC_OUT") + sync_run=$(fm_nm_field "$SYNC_OUT" run) + if [ "$sync_state" = pipeline_owned ] || [ "$sync_state" = synchronized ]; then + if [ -n "$sync_run" ] && [ "$sync_run" = "$run_id" ]; then + branch_sync_state=$sync_state + # Cross-check the run's own submitted and current heads when axi + # sync reports them; this is the authoritative run-owned evidence. + sync_submitted=$(fm_nm_field "$SYNC_OUT" submitted_head) + sync_current=$(fm_nm_field "$SYNC_OUT" current_head) + if [ -n "$sync_submitted" ]; then + if [ "$(fm_nm_resolve_head "$worktree" "$sync_submitted" || true)" != "$validated_head" ]; then + branch_sync_state= + fi + fi + if [ -n "$sync_current" ]; then + if [ "$(fm_nm_resolve_head "$worktree" "$sync_current" || true)" != "$observed_head_full" ]; then + branch_sync_state= + fi + fi + fi + fi + fi + fi + if [ "$branch_sync_state" != pipeline_owned ] && [ "$branch_sync_state" != synchronized ]; then release_validation_lock if [ "$restamp_accounted" -eq 1 ]; then echo "error: accepted restamp lacks authoritative pipeline ownership" >&2 diff --git a/docs/verification/evidence-receipts.md b/docs/verification/evidence-receipts.md index 006ca9fc885..c442e8b2709 100644 --- a/docs/verification/evidence-receipts.md +++ b/docs/verification/evidence-receipts.md @@ -36,11 +36,11 @@ The exact receipt key and type schema is owned by the header and `--help` output - Findings that invalidate a receipt or acceptance claim atomically bind one generation-scoped idempotent finding-to-criterion marker to the invalidation-time head and receipt boundary, then require a strict non-empty descendant delta and a later successful receipt bound to the new head before replanning or completion. - One pinned state-directory owner snapshots single-link no-follow metadata and performs compare-bound atomic replacements for every validation metadata update. - PR registration publishes canonical PR identity and its validation publication generation through one compare-bound pinned metadata replacement after the watcher artifacts publish, and revokes those artifacts if that replacement fails. -- Successful planned-head or faithful-restamp runs can bind after reaching checks-passed or passed, while failed and cancelled runs remain ineligible. +- Successful planned-head, faithful-restamp, or pipeline-owned-descendant runs can bind after reaching checks-passed or passed, or while still actively owned by the pipeline, 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 No-Mistakes run proves either a descendant of the planned head or a faithful restamp of its validation-base-to-planned chain: an active run must currently own the branch, while a terminal passed run proves the advance through its own reported head. -- A chain the pipeline's rebase step restamped binds and completes only when it is a faithful restamp of the planned chain from the recorded validation base and passes the same branch and pipeline-ownership checks in either custody shape. +- A changed worktree head invalidates completion unless the bound No-Mistakes run proves the current content is accounted for by the planned chain: a strict descendant of the planned head, a faithful restamp of the validation-base-to-planned chain, or a strict descendant of such a faithful restamp; active runs must prove pipeline ownership through branch_sync or `axi sync --check`, while terminal passed runs prove the advance through their own reported head. +- A chain the pipeline's rebase step restamped binds and completes only when it is a faithful restamp of the planned chain from the recorded validation base, and a restamped chain followed by additional run-owned commits binds and completes as a run-owned descendant that preserves the same branch and pipeline-ownership checks. - 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. @@ -50,19 +50,21 @@ The exact receipt key and type schema is owned by the header and `--help` output ## Reconciliation with the descendant-advance guarantee -The descendant-advance guarantee above admits one shape of head change: new commits landed on top of the planned head, so the planned head stays an ancestor of the current head. -The no-mistakes rebase step produces a second shape it does not admit. -That step re-commits every commit on the branch with a fresh committer stamp, which mints a new object id for the whole chain while every tree stays byte-identical, so the planned head stops being an ancestor of anything the run reports. +The descendant-advance guarantee above admits three shapes of head change, and no others. +The first shape is a strict descendant: new commits landed on top of the planned head, so the planned head stays an ancestor of the current head. +The second shape is a faithful restamp: the no-mistakes rebase step re-commits every commit on the branch with a fresh committer stamp, which mints a new object id for the whole chain while every tree stays byte-identical, so the planned head stops being an ancestor of anything the run reports. Observed on 2026-09-05 in run `01M1RW6JNH5C5VN15PPRYDW3J0`: planned head `874ce334` and run head `bd8aaff5` both carry tree `f7d8fa3a`, and `git merge-base --is-ancestor 874ce334 bd8aaff5` exits 1. +The third shape is a restamped chain followed by additional run-owned commits: the pipeline first restamps the planned chain, then adds review or document commits on top, so the current head is a strict descendant of a faithful restamp. -Three checks were relaxed to admit that shape, and no others. -`--bind-run` accepted only a run head that resolved to the planned head, and now also accepts a head that faithfully restamps the validation-base-to-planned chain, which it then records as `validation_run_head`. -`--complete` required the run's reported head to be the current worktree head, and now also accepts run and current heads that both faithfully restamp the validation-base-to-planned chain, which is what a run reports after its custody return puts the branch back on the pre-rebase chain. -`--complete` required the current head to descend from the planned head, and now also accepts a current head that faithfully restamps the validation-base-to-planned chain. +The relaxed checks use one shared content-identity predicate, `fm_nm_head_is_accounted` in `bin/fm-nm-run-lib.sh`. +`--bind-run` accepts the planned head, a faithful restamp of the validation-base-to-planned chain, or a strict descendant of either, but only when the run is active and the pipeline owns the branch, or the run is terminal and passed. +`--complete` accepts the same shapes, with the same branch and ownership requirements. +A descendant is accepted only when the run reports the same task branch and, for active runs, `branch_sync.state` is `pipeline_owned` or `synchronized`; when `axi status` omits `branch_sync`, `axi sync --check` supplies the authoritative run-owned head evidence and `submitted_head`/`current_head` cross-check. -Chain provenance is the content-identity mechanism, stated once in `fm_nm_head_is_faithful_restamp` in `bin/fm-nm-run-lib.sh`. +Chain provenance is the content-identity mechanism, stated in `fm_nm_head_is_faithful_restamp` in `bin/fm-nm-run-lib.sh`. It resolves the recorded validation base, requires it to be an ancestor of both heads, requires equal commit counts, and compares each corresponding commit tree in base-to-head order. -Foreign drift, unrelated same-tree tips, reverted foreign commits, and rebases onto changed bases stay refused because they break ancestry, count, or a pairwise tree comparison. +The descendant-of-restamp check extends this by taking the leading segment of the candidate's first-parent chain and requiring that segment to be a faithful restamp, with at least one additional commit after it. +Foreign drift, unrelated same-tree tips, reverted foreign commits, rebases onto changed bases, and unowned or mismatched branches stay refused because they break ancestry, count, tree comparison, branch identity, or run ownership. Every other completion requirement is unchanged: the run must still be the bound run at the current generation, still be genuinely passed or checks-green, and still report the current worktree branch while active with pipeline ownership or be terminal PASSED otherwise. ## Known limitations @@ -72,7 +74,7 @@ Every other completion requirement is unchanged: the run must still be the bound ## Verification environment -- Date: 2026-09-05. +- Date: 2026-09-11. - ShellCheck: 0.11.0. - Git: 2.34.1. @@ -114,6 +116,12 @@ ok - binding and completion work against the real agent-supplied intent-log shap ok - terminal passed runs seal their own pipeline advance and refuse foreign drift ok - pipeline rebase restamps bind and seal their validated content ok - restamped chains enforce provenance and ownership +ok - active pipeline-owned descendant binds without replan +ok - terminal pipeline-owned descendant binds and completes +ok - restamp chain followed by pipeline doc commit binds and completes +ok - active descendant binds using axi sync fallback when axi status omits branch_sync +ok - unowned active descendant binding is rejected +ok - descendant bind rejects the wrong branch 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 diff --git a/tests/fm-receipt-check.test.sh b/tests/fm-receipt-check.test.sh index f3c86ef26ca..a3196b788b8 100755 --- a/tests/fm-receipt-check.test.sh +++ b/tests/fm-receipt-check.test.sh @@ -22,6 +22,7 @@ cat > "$FAKE_NO_MISTAKES" <<'EOF' case "$*" in *"axi logs --step intent --run "*) printf '%s\n' "${FM_FAKE_NM_INTENT:-}" ;; *"axi logs --step ci --run "*) printf '%s\n' "${FM_FAKE_NM_CI_LOG:-}" ;; + *"axi sync"*) printf '%s\n' "${FM_FAKE_NM_SYNC:-$FM_FAKE_NM_STATUS}" ;; *) printf '%s\n' "$FM_FAKE_NM_STATUS" ;; esac EOF @@ -37,7 +38,7 @@ chmod +x "$FAIL_NO_MISTAKES" nm_status() { # local status if [ "$3" = passed ]; then status=completed; else status=running; fi - printf 'run:\n id: "%s"\n status: %s\n head: "%s"\noutcome: %s\n' "$1" "$status" "$2" "$3" + printf 'run:\n id: "%s"\n branch: fm/%s\n status: %s\n head: "%s"\noutcome: %s\n' "$1" "${id:-}" "$status" "$2" "$3" } nm_pipeline_status() { # @@ -45,6 +46,11 @@ nm_pipeline_status() { # + printf 'branch_sync:\n state: %s\nlocal:\n branch: %s\n head: "%s"\npipeline:\n run: "%s"\n status: running\n submitted_head: "%s"\n current_head: "%s"\n' \ + "$5" "$2" "$4" "$1" "$3" "$4" +} + test_help_advertises_generation_bound_run_binding() { local out out=$("$CHECK" --help) || fail "receipt checker help failed" @@ -1269,6 +1275,149 @@ test_restamped_chains_refuse_foreign_content_and_unowned_rewrites() { pass "restamped chains enforce provenance and ownership" } +test_active_pipeline_owned_descendant_binds_without_replan() { + local id=receipt-active-descendant base project initial_head current_head generation status + 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 "active descendant 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_pipeline_status RUN-active-descendant "fm/$id" "$initial_head" ci '' pipeline_owned) + FM_FAKE_NM_STATUS="$status" FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --bind-run RUN-active-descendant --generation "$generation" >/dev/null \ + || fail "active descendant initial binding failed" + printf 'pipeline fix\n' >> "$project/src/app.sh" + git -C "$project" add src/app.sh + git -C "$project" commit -q -m 'no-mistakes: apply CI fixes' + current_head=$(git -C "$project" rev-parse HEAD) + status=$(nm_pipeline_status RUN-active-descendant "fm/$id" "$current_head" ci '' pipeline_owned) + FM_FAKE_NM_STATUS="$status" FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --bind-run RUN-active-descendant --generation "$generation" >/dev/null \ + || fail "active pipeline-owned descendant was not bound without replanning" + pass "active pipeline-owned descendant binds without replan" +} + +test_terminal_pipeline_owned_descendant_binds_and_completes() { + local id=receipt-terminal-descendant base project initial_head current_head generation status out + 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 descendant 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_pipeline_status RUN-terminal-descendant "fm/$id" "$initial_head" ci '' pipeline_owned) + FM_FAKE_NM_STATUS="$status" FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --bind-run RUN-terminal-descendant --generation "$generation" >/dev/null \ + || fail "terminal descendant initial 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-descendant "fm/$id" "$current_head" completed passed agent_owned) + FM_FAKE_NM_STATUS="$status" FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --bind-run RUN-terminal-descendant --generation "$generation" >/dev/null \ + || fail "terminal descendant was not bound" + 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 descendant completion failed" + printf '%s' "$out" | jq -e --arg head "$current_head" '.status == "completed" and .completed_head == $head' >/dev/null \ + || fail "terminal descendant completion did not record the advanced head" + pass "terminal pipeline-owned descendant binds and completes" +} + +test_pipeline_rebase_restamp_plus_doc_commit_binds_and_completes() { + local id=receipt-restamp-doc base project validated_head restamped current_head generation status out + id=receipt-restamp-doc + read -r base project validated_head generation < <(plan_restamp_fixture "$id") + restamped=$(restamp_chain "$project" "$base") + [ "$(git -C "$project" rev-parse "$restamped^{tree}")" = "$(git -C "$project" rev-parse "$validated_head^{tree}")" ] \ + || fail "restamp fixture did not preserve the validated content" + printf 'doc commit after rebase\n' >> "$project/src/app.sh" + git -C "$project" add src/app.sh + git -C "$project" commit -q -m 'no-mistakes: document the rebase' + current_head=$(git -C "$project" rev-parse HEAD) + status=$(nm_pipeline_status RUN-restamp-doc "fm/$id" "$current_head" ci '' pipeline_owned) + FM_FAKE_NM_STATUS="$status" FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --bind-run RUN-restamp-doc --generation "$generation" >/dev/null \ + || fail "restamp-plus-doc descendant binding failed" + out=$(FM_FAKE_NM_STATUS="$status" FM_FAKE_NM_CI_LOG='all CI checks passed - still monitoring' \ + FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --complete --terminal-evidence no-mistakes-passed) \ + || fail "restamp-plus-doc completion failed" + printf '%s' "$out" | jq -e --arg head "$current_head" '.status == "completed" and .completed_head == $head' >/dev/null \ + || fail "restamp-plus-doc completion did not record the advanced head" + pass "restamp chain followed by pipeline doc commit binds and completes" +} + +test_active_descendant_bind_via_axi_sync_fallback() { + local id=receipt-axi-sync-fallback base project initial_head current_head generation status sync + 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 "axi sync fallback 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-) + printf 'pipeline fix\n' >> "$project/src/app.sh" + git -C "$project" add src/app.sh + git -C "$project" commit -q -m 'no-mistakes: apply CI fixes' + current_head=$(git -C "$project" rev-parse HEAD) + status=$(nm_status RUN-axi-sync-fallback "$current_head" '') + sync=$(nm_sync_status RUN-axi-sync-fallback "fm/$id" "$initial_head" "$current_head" synchronized) + FM_FAKE_NM_STATUS="$status" FM_FAKE_NM_SYNC="$sync" FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --bind-run RUN-axi-sync-fallback --generation "$generation" >/dev/null \ + || fail "active descendant was not bound via axi sync fallback" + pass "active descendant binds using axi sync fallback when axi status omits branch_sync" +} + +test_unowned_active_descendant_bind_rejected() { + local id=receipt-unowned-descendant base project current_head generation status rc + 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 "unowned descendant plan failed" + project="$TMP_ROOT/project-$id" + generation=$(grep '^validation_generation=' "$HOME_DIR/state/$id.meta" | tail -1 | cut -d= -f2-) + printf 'unproven change\n' >> "$project/src/app.sh" + git -C "$project" add src/app.sh + git -C "$project" commit -q -m 'unproven' + current_head=$(git -C "$project" rev-parse HEAD) + status=$(nm_pipeline_status RUN-unowned-descendant "fm/$id" "$current_head" ci '' manual) + FM_FAKE_NM_STATUS="$status" FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --bind-run RUN-unowned-descendant --generation "$generation" >/dev/null 2>&1 + rc=$? + expect_code 2 "$rc" "bind accepted an unowned active descendant" + pass "unowned active descendant binding is rejected" +} + +test_descendant_bind_rejects_wrong_branch() { + local id=receipt-descendant-wrong-branch base project current_head generation status rc + 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 "wrong-branch descendant plan failed" + project="$TMP_ROOT/project-$id" + generation=$(grep '^validation_generation=' "$HOME_DIR/state/$id.meta" | tail -1 | cut -d= -f2-) + printf 'other branch change\n' >> "$project/src/app.sh" + git -C "$project" add src/app.sh + git -C "$project" commit -q -m 'change' + current_head=$(git -C "$project" rev-parse HEAD) + status=$(nm_pipeline_status RUN-wrong-branch "someone-elses-branch" "$current_head" ci '' pipeline_owned) + FM_FAKE_NM_STATUS="$status" FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ + "$CHECK" "$id" --bind-run RUN-wrong-branch --generation "$generation" >/dev/null 2>&1 + rc=$? + expect_code 2 "$rc" "bind accepted a descendant on the wrong branch" + pass "descendant bind rejects the wrong branch" +} + 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" @@ -1823,6 +1972,12 @@ test_completion_accepts_only_pipeline_owned_head_advance test_terminal_passed_run_seals_its_own_pipeline_head_advance test_pipeline_rebase_restamp_binds_and_seals_identical_content test_restamped_chains_refuse_foreign_content_and_unowned_rewrites +test_active_pipeline_owned_descendant_binds_without_replan +test_terminal_pipeline_owned_descendant_binds_and_completes +test_pipeline_rebase_restamp_plus_doc_commit_binds_and_completes +test_active_descendant_bind_via_axi_sync_fallback +test_unowned_active_descendant_bind_rejected +test_descendant_bind_rejects_wrong_branch test_low_risk_skips_no_mistakes_under_explicit_policy test_low_risk_requires_safe_prose_and_applicable_evidence test_implementation_completion_precedes_planning From 2d94213a5c97b6437b6c6f196fc0380a7bbd46cc Mon Sep 17 00:00:00 2001 From: dnth Date: Fri, 11 Sep 2026 01:51:56 +0800 Subject: [PATCH 2/6] no-mistakes(review): Restore strict pipeline-owned active-run attribution --- bin/fm-nm-run-lib.sh | 5 +---- tests/fm-crew-state.test.sh | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/bin/fm-nm-run-lib.sh b/bin/fm-nm-run-lib.sh index ce00ffbe47f..a7bcd4b4e06 100644 --- a/bin/fm-nm-run-lib.sh +++ b/bin/fm-nm-run-lib.sh @@ -206,10 +206,7 @@ fm_nm_head_resolvable() { # # is absent (no run on the current branch, another branch's run, or a CLI # without branch sync). fm_nm_run_is_pipeline_owned_active() { # - case "$(fm_nm_branch_sync_state "$1")" in - pipeline_owned|synchronized) ;; - *) return 1 ;; - esac + [ "$(fm_nm_branch_sync_state "$1")" = pipeline_owned ] || return 1 fm_nm_run_is_active "$1" } diff --git a/tests/fm-crew-state.test.sh b/tests/fm-crew-state.test.sh index 019b4e8c050..504f2d46563 100755 --- a/tests/fm-crew-state.test.sh +++ b/tests/fm-crew-state.test.sh @@ -1456,7 +1456,7 @@ test_non_pipeline_owned_unresolvable_head_not_attributed() { make_fakebin "$d" >/dev/null fm_write_meta "$d/state/feat-f10d.meta" "window=fm:fm-feat-f10d" "worktree=$d/wt" "kind=ship" "harness=claude" printf 'working: implementing\n' > "$d/state/feat-f10d.status" - FM_FAKE_AXI_STATUS="$(run_running_pipeline_owned fm/feat-f10d f0f0f0f0 synced)" + FM_FAKE_AXI_STATUS="$(run_running_pipeline_owned fm/feat-f10d f0f0f0f0 synchronized)" FM_FAKE_RUNS_LIST="" FM_FAKE_BUSY=0 arm_idle_record "$d/state" feat-f10d From 22473b2ed52817a27fb82c704a3176c47bd6945b Mon Sep 17 00:00:00 2001 From: dnth Date: Fri, 11 Sep 2026 02:02:44 +0800 Subject: [PATCH 3/6] no-mistakes(document): Updated validation head-accounting documentation comments --- bin/fm-receipt-check.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/bin/fm-receipt-check.sh b/bin/fm-receipt-check.sh index 16e012bd0fe..c66bbf718f3 100755 --- a/bin/fm-receipt-check.sh +++ b/bin/fm-receipt-check.sh @@ -44,17 +44,17 @@ # 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 No-Mistakes run accounts for the current content, in -# one of two shapes. A descendant of the latest validation_head is proved by -# pipeline ownership while the run is active and by the run's own reported head -# once it is terminal and PASSED, so a terminal run needs no replan and no fresh -# run to seal its own pipeline commits. A chain the pipeline's rebase step -# restamped is proved as a faithful restamp of the validation-base-to-head -# chain, because that step re-commits every branch commit with a fresh committer -# stamp and so reports a head that is neither validation_head nor a descendant -# of it; --bind-run accepts and records that same restamped head. Foreign -# commits landed after the run still refuse completion because they break the -# chain's ancestry, count, or pairwise tree identity. +# drift unless the bound No-Mistakes run accounts for the current content in one +# of three shapes: a strict descendant of the latest validation_head, a faithful +# restamp of the validation-base-to-head chain, or a strict descendant of such a +# restamp. Active descendants require current pipeline ownership and terminal +# passed runs prove the advance through their own reported head, so a terminal +# run needs no replan or fresh run to seal its own pipeline commits. A chain the +# pipeline's rebase step restamped is proved by matching every corresponding +# commit tree, even though fresh committer stamps make the reported head neither +# validation_head nor its descendant. Foreign commits still refuse completion +# because they break ancestry, count, or pairwise tree identity, or lack the +# required run-owned branch evidence. # When --plan returns path=receipts-mechanical, append fresh successful mechanical # evidence for every changed file with: # From 8146f461e13e52534a844cabf9231184f2fe8687 Mon Sep 17 00:00:00 2001 From: dnth Date: Fri, 11 Sep 2026 02:49:54 +0800 Subject: [PATCH 4/6] no-mistakes(review): Require pipeline-owned state for descendant validation --- bin/fm-receipt-check.sh | 16 ++++++++-------- tests/fm-receipt-check.test.sh | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bin/fm-receipt-check.sh b/bin/fm-receipt-check.sh index c66bbf718f3..5f8b88f9444 100755 --- a/bin/fm-receipt-check.sh +++ b/bin/fm-receipt-check.sh @@ -694,14 +694,14 @@ if [ "$ACTION" = bind-run ]; then BIND_HEAD_ACCOUNTED=1 elif fm_nm_run_is_active "$BIND_OUT"; then branch_sync_state=$(fm_nm_branch_sync_state "$BIND_OUT") - if [ "$branch_sync_state" != pipeline_owned ] && [ "$branch_sync_state" != synchronized ]; then + if [ "$branch_sync_state" != pipeline_owned ]; then # Real no-mistakes `axi status` for an active run does not include a # branch_sync block; use `axi sync --check` to confirm pipeline ownership. SYNC_OUT=$(fm_nm_run_checked "$BIND_WORKTREE" "$NM_TIMEOUT" axi sync --check) || SYNC_OUT= if [ -n "$SYNC_OUT" ]; then sync_state=$(fm_nm_branch_sync_state "$SYNC_OUT") sync_run=$(fm_nm_field "$SYNC_OUT" run) - if [ "$sync_state" = pipeline_owned ] || [ "$sync_state" = synchronized ]; then + if [ "$sync_state" = pipeline_owned ]; then if [ -n "$sync_run" ] && [ "$sync_run" = "$RUN_ID_INPUT" ]; then branch_sync_state=$sync_state # Cross-check the run's own submitted and current heads when axi @@ -722,7 +722,7 @@ if [ "$ACTION" = bind-run ]; then fi fi fi - if [ "$branch_sync_state" = pipeline_owned ] || [ "$branch_sync_state" = synchronized ]; then + if [ "$branch_sync_state" = pipeline_owned ]; then BIND_HEAD_ACCOUNTED=1 fi fi @@ -898,16 +898,16 @@ record_validation_completed() { # The advance is authoritative only while the run is ACTIVE and the # pipeline owns the branch, or once the run has reached a terminal PASSED # state and released the branch. Active ownership is shown by a - # branch_sync state of pipeline_owned or synchronized, either directly in - # the axi status output or in `axi sync --check` for current no-mistakes. + # branch_sync state of pipeline_owned, either directly in the axi status + # output or in `axi sync --check` for current no-mistakes. if fm_nm_run_is_active "$run_out"; then branch_sync_state=$(fm_nm_branch_sync_state "$run_out") - if [ "$branch_sync_state" != pipeline_owned ] && [ "$branch_sync_state" != synchronized ]; then + if [ "$branch_sync_state" != pipeline_owned ]; then SYNC_OUT=$(fm_nm_run_checked "$worktree" "$NM_TIMEOUT" axi sync --check) || SYNC_OUT= if [ -n "$SYNC_OUT" ]; then sync_state=$(fm_nm_branch_sync_state "$SYNC_OUT") sync_run=$(fm_nm_field "$SYNC_OUT" run) - if [ "$sync_state" = pipeline_owned ] || [ "$sync_state" = synchronized ]; then + if [ "$sync_state" = pipeline_owned ]; then if [ -n "$sync_run" ] && [ "$sync_run" = "$run_id" ]; then branch_sync_state=$sync_state # Cross-check the run's own submitted and current heads when axi @@ -928,7 +928,7 @@ record_validation_completed() { fi fi fi - if [ "$branch_sync_state" != pipeline_owned ] && [ "$branch_sync_state" != synchronized ]; then + if [ "$branch_sync_state" != pipeline_owned ]; then release_validation_lock if [ "$restamp_accounted" -eq 1 ]; then echo "error: accepted restamp lacks authoritative pipeline ownership" >&2 diff --git a/tests/fm-receipt-check.test.sh b/tests/fm-receipt-check.test.sh index a3196b788b8..3e39fade89e 100755 --- a/tests/fm-receipt-check.test.sh +++ b/tests/fm-receipt-check.test.sh @@ -1369,7 +1369,7 @@ test_active_descendant_bind_via_axi_sync_fallback() { git -C "$project" commit -q -m 'no-mistakes: apply CI fixes' current_head=$(git -C "$project" rev-parse HEAD) status=$(nm_status RUN-axi-sync-fallback "$current_head" '') - sync=$(nm_sync_status RUN-axi-sync-fallback "fm/$id" "$initial_head" "$current_head" synchronized) + sync=$(nm_sync_status RUN-axi-sync-fallback "fm/$id" "$initial_head" "$current_head" pipeline_owned) FM_FAKE_NM_STATUS="$status" FM_FAKE_NM_SYNC="$sync" FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ "$CHECK" "$id" --bind-run RUN-axi-sync-fallback --generation "$generation" >/dev/null \ || fail "active descendant was not bound via axi sync fallback" @@ -1389,7 +1389,7 @@ test_unowned_active_descendant_bind_rejected() { git -C "$project" add src/app.sh git -C "$project" commit -q -m 'unproven' current_head=$(git -C "$project" rev-parse HEAD) - status=$(nm_pipeline_status RUN-unowned-descendant "fm/$id" "$current_head" ci '' manual) + status=$(nm_pipeline_status RUN-unowned-descendant "fm/$id" "$current_head" ci '' synchronized) FM_FAKE_NM_STATUS="$status" FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ "$CHECK" "$id" --bind-run RUN-unowned-descendant --generation "$generation" >/dev/null 2>&1 rc=$? From 332250242dbfdb822b1c8c1f267c7f63103c4226 Mon Sep 17 00:00:00 2001 From: dnth Date: Fri, 11 Sep 2026 03:03:31 +0800 Subject: [PATCH 5/6] no-mistakes(review): Enforce branch identity and strict pipeline ownership --- bin/fm-receipt-check.sh | 12 ++++++++---- docs/verification/evidence-receipts.md | 2 +- tests/fm-receipt-check.test.sh | 8 ++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/bin/fm-receipt-check.sh b/bin/fm-receipt-check.sh index 5f8b88f9444..b3754a1ac46 100755 --- a/bin/fm-receipt-check.sh +++ b/bin/fm-receipt-check.sh @@ -674,6 +674,11 @@ if [ "$ACTION" = bind-run ]; then passed:*|checks-passed:*|*:passed|*:checks-passed) BIND_STATE_OK=1 ;; running:*|fixing:*|ci:*|awaiting_approval:*) BIND_STATE_OK=1 ;; esac + BIND_RUN_BRANCH=$(fm_nm_field "$BIND_OUT" branch) + BIND_BRANCH_MATCH=0 + if [ -n "$BIND_RUN_BRANCH" ] && fm_nm_branch_matches_worktree "$BIND_WORKTREE" "$BIND_RUN_BRANCH"; then + BIND_BRANCH_MATCH=1 + fi # The run's head is the planned commit itself, a faithful restamp of the # validated chain, or a proven pipeline-owned descendant that advanced after # the plan was recorded (review/doc/lint fix commits). Allow descendants so @@ -682,14 +687,13 @@ if [ "$ACTION" = bind-run ]; then BIND_HEAD_ACCOUNTED=0 if [ -n "$BIND_RUN_HEAD" ]; then if [ "$BIND_RUN_HEAD" = "$BIND_HEAD" ]; then - BIND_HEAD_ACCOUNTED=1 + [ "$BIND_BRANCH_MATCH" -eq 1 ] && BIND_HEAD_ACCOUNTED=1 elif fm_nm_head_is_faithful_restamp "$BIND_WORKTREE" "$BIND_BASE" "$BIND_HEAD" "$BIND_RUN_HEAD"; then - BIND_HEAD_ACCOUNTED=1 + [ "$BIND_BRANCH_MATCH" -eq 1 ] && BIND_HEAD_ACCOUNTED=1 elif fm_nm_head_is_accounted "$BIND_WORKTREE" "$BIND_BASE" "$BIND_HEAD" "$BIND_RUN_HEAD"; then # The head advanced after the plan; require branch identity and active or # terminal passed ownership so an unrelated descendant cannot bind. - run_branch=$(fm_nm_field "$BIND_OUT" branch) - if [ -n "$run_branch" ] && fm_nm_branch_matches_worktree "$BIND_WORKTREE" "$run_branch"; then + if [ "$BIND_BRANCH_MATCH" -eq 1 ]; then if fm_nm_run_is_terminal_passed "$BIND_OUT"; then BIND_HEAD_ACCOUNTED=1 elif fm_nm_run_is_active "$BIND_OUT"; then diff --git a/docs/verification/evidence-receipts.md b/docs/verification/evidence-receipts.md index c442e8b2709..ac476f5a2fc 100644 --- a/docs/verification/evidence-receipts.md +++ b/docs/verification/evidence-receipts.md @@ -59,7 +59,7 @@ The third shape is a restamped chain followed by additional run-owned commits: t The relaxed checks use one shared content-identity predicate, `fm_nm_head_is_accounted` in `bin/fm-nm-run-lib.sh`. `--bind-run` accepts the planned head, a faithful restamp of the validation-base-to-planned chain, or a strict descendant of either, but only when the run is active and the pipeline owns the branch, or the run is terminal and passed. `--complete` accepts the same shapes, with the same branch and ownership requirements. -A descendant is accepted only when the run reports the same task branch and, for active runs, `branch_sync.state` is `pipeline_owned` or `synchronized`; when `axi status` omits `branch_sync`, `axi sync --check` supplies the authoritative run-owned head evidence and `submitted_head`/`current_head` cross-check. +A descendant is accepted only when the run reports the same task branch and, for active runs, `branch_sync.state` is `pipeline_owned`; when `axi status` omits `branch_sync`, `axi sync --check` supplies the authoritative run-owned head evidence and `submitted_head`/`current_head` cross-check. Chain provenance is the content-identity mechanism, stated in `fm_nm_head_is_faithful_restamp` in `bin/fm-nm-run-lib.sh`. It resolves the recorded validation base, requires it to be an ancestor of both heads, requires equal commit counts, and compares each corresponding commit tree in base-to-head order. diff --git a/tests/fm-receipt-check.test.sh b/tests/fm-receipt-check.test.sh index 3e39fade89e..699be088002 100755 --- a/tests/fm-receipt-check.test.sh +++ b/tests/fm-receipt-check.test.sh @@ -494,7 +494,7 @@ test_ci_green_log_allows_exact_bound_run_completion() { FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ "$CHECK" "$id" --bind-run RUN-ci-log --generation "$generation" >/dev/null \ || fail "CI-log readiness fixture run binding failed" - ci_status=$(printf 'run:\n id: "RUN-ci-log"\n status: ci\n head: "%s"\noutcome: pending\n' "$head") + ci_status=$(printf 'run:\n id: "RUN-ci-log"\n branch: fm/%s\n status: ci\n head: "%s"\noutcome: pending\n' "$id" "$head") FM_FAKE_NM_STATUS="$ci_status" FM_FAKE_NM_CI_LOG='CI checks running' \ FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ "$CHECK" "$id" --complete --terminal-evidence no-mistakes-passed >/dev/null 2>&1 @@ -824,7 +824,7 @@ test_terminal_and_failed_runs_bind_by_current_plan() { project="$TMP_ROOT/project-$id" head=$(git -C "$project" rev-parse HEAD) generation=$(grep '^validation_generation=' "$HOME_DIR/state/$id.meta" | tail -1 | cut -d= -f2-) - terminal=$(printf 'run:\n id: "RUN-terminal"\n status: completed\n head: "%s"\noutcome: checks-passed\n' "$head") + terminal=$(printf 'run:\n id: "RUN-terminal"\n branch: fm/%s\n status: completed\n head: "%s"\noutcome: checks-passed\n' "$id" "$head") FM_FAKE_NM_STATUS="$terminal" 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 --generation "$generation" >/dev/null \ @@ -839,7 +839,7 @@ test_terminal_and_failed_runs_bind_by_current_plan() { project="$TMP_ROOT/project-$id" head=$(git -C "$project" rev-parse HEAD) generation=$(grep '^validation_generation=' "$HOME_DIR/state/$id.meta" | tail -1 | cut -d= -f2-) - failed=$(printf 'run:\n id: "RUN-failed"\n status: failed\n head: "%s"\noutcome: failed\n' "$head") + failed=$(printf 'run:\n id: "RUN-failed"\n branch: fm/%s\n status: failed\n head: "%s"\noutcome: failed\n' "$id" "$head") FM_FAKE_NM_STATUS="$failed" 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 --generation "$generation" >/dev/null 2>&1 @@ -1892,7 +1892,7 @@ test_agent_supplied_intent_log_binds_and_completes() { FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ "$CHECK" "$id" --bind-run RUN-agent-intent --generation "$generation" >/dev/null \ || fail "binding failed against the real agent-supplied intent-log shape" - ci_status=$(printf 'run:\n id: "RUN-agent-intent"\n status: ci\n head: "%s"\noutcome: pending\n' "$head") + ci_status=$(printf 'run:\n id: "RUN-agent-intent"\n branch: fm/%s\n status: ci\n head: "%s"\noutcome: pending\n' "$id" "$head") FM_FAKE_NM_STATUS="$ci_status" FM_FAKE_NM_CI_LOG='all CI checks passed - still monitoring until merged or closed' \ FM_NO_MISTAKES_BIN="$FAKE_NO_MISTAKES" FM_HOME="$HOME_DIR" \ "$CHECK" "$id" --complete --terminal-evidence no-mistakes-passed >/dev/null \ From c3585feb4f994d08615c179bbc19f1bd2ca51bd0 Mon Sep 17 00:00:00 2001 From: dnth Date: Fri, 11 Sep 2026 03:16:45 +0800 Subject: [PATCH 6/6] no-mistakes(document): Refresh validation binding documentation accuracy --- docs/verification/evidence-receipts.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/verification/evidence-receipts.md b/docs/verification/evidence-receipts.md index ac476f5a2fc..67a54b70af1 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-09-05. +This record captures the active maintainer evidence for ship-task acceptance receipts and conservative validation routing as of 2026-09-11. 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 @@ -36,7 +36,7 @@ The exact receipt key and type schema is owned by the header and `--help` output - Findings that invalidate a receipt or acceptance claim atomically bind one generation-scoped idempotent finding-to-criterion marker to the invalidation-time head and receipt boundary, then require a strict non-empty descendant delta and a later successful receipt bound to the new head before replanning or completion. - One pinned state-directory owner snapshots single-link no-follow metadata and performs compare-bound atomic replacements for every validation metadata update. - PR registration publishes canonical PR identity and its validation publication generation through one compare-bound pinned metadata replacement after the watcher artifacts publish, and revokes those artifacts if that replacement fails. -- Successful planned-head, faithful-restamp, or pipeline-owned-descendant runs can bind after reaching checks-passed or passed, or while still actively owned by the pipeline, while failed and cancelled runs remain ineligible. +- Successful planned-head and faithful-restamp runs can bind with checks-passed, passed, or eligible active status, while descendants require active pipeline ownership or a terminal passed run; 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 No-Mistakes run proves the current content is accounted for by the planned chain: a strict descendant of the planned head, a faithful restamp of the validation-base-to-planned chain, or a strict descendant of such a faithful restamp; active runs must prove pipeline ownership through branch_sync or `axi sync --check`, while terminal passed runs prove the advance through their own reported head. @@ -57,8 +57,8 @@ Observed on 2026-09-05 in run `01M1RW6JNH5C5VN15PPRYDW3J0`: planned head `874ce3 The third shape is a restamped chain followed by additional run-owned commits: the pipeline first restamps the planned chain, then adds review or document commits on top, so the current head is a strict descendant of a faithful restamp. The relaxed checks use one shared content-identity predicate, `fm_nm_head_is_accounted` in `bin/fm-nm-run-lib.sh`. -`--bind-run` accepts the planned head, a faithful restamp of the validation-base-to-planned chain, or a strict descendant of either, but only when the run is active and the pipeline owns the branch, or the run is terminal and passed. -`--complete` accepts the same shapes, with the same branch and ownership requirements. +`--bind-run` accepts the planned head or a faithful restamp when the run reports the task branch and has an eligible checks-passed or active status; a strict descendant of either additionally requires active pipeline ownership or a terminal passed run. +`--complete` accepts the same shapes, with branch identity and ownership required whenever the run advanced beyond the planned head. A descendant is accepted only when the run reports the same task branch and, for active runs, `branch_sync.state` is `pipeline_owned`; when `axi status` omits `branch_sync`, `axi sync --check` supplies the authoritative run-owned head evidence and `submitted_head`/`current_head` cross-check. Chain provenance is the content-identity mechanism, stated in `fm_nm_head_is_faithful_restamp` in `bin/fm-nm-run-lib.sh`.