Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions bin/fm-nm-run-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,25 @@ fm_nm_run_is_pipeline_owned_active() { # <toon-output>
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() { # <toon-output>
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:
Expand Down
29 changes: 22 additions & 7 deletions bin/fm-receipt-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@
# The resolved validation_tier, validation_path, reason code, base, head, size,
# and start time are appended to state/<task-id>.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:
#
Expand Down Expand Up @@ -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
Expand Down
14 changes: 8 additions & 6 deletions docs/verification/evidence-receipts.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -53,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.

Expand Down Expand Up @@ -90,19 +91,20 @@ 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 - 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
Expand Down
87 changes: 87 additions & 0 deletions tests/fm-receipt-check.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
Loading