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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,8 @@ Send the same worker one exact decision naming the decision key, step, action, a
Require the matching `resolved` event, forbid `--yes`, and require the worker to process every synchronous return until completion or a genuinely new escalation.
Resume fleet supervision immediately after the decision lands.

Judge validation by the currently attributed run step through `bin/fm-crew-state.sh`, not by shell liveness or the last status event.
Running, fixing, or CI states remain working; parked approval or fix-review states require the worker to follow the active gate help; passed or checks-passed is done; failed or cancelled is failed exactly as `bin/fm-crew-state.sh` prints it - only that state line reclassifies an orphaned ci monitor after green checks as held-for-merge done, or a run record the `daemon status` probe leaves unverified as unknown, never the raw run record.
Judge validation by the resolved state line from [`bin/fm-crew-state.sh`](bin/fm-crew-state.sh), whose header owns outcome mappings and CI-monitor/daemon exceptions, never by shell liveness, the last status event, or a raw run record.
Workers parked at approval or fix-review must follow the active gate help.
A worker hand-editing, committing, aborting, or restarting during an active validation run duplicates pipeline ownership outside the supersession sequence above; steer it back to the gate response flow.
The worker reports the PR when CI first becomes green rather than waiting for merge monitoring to finish.

Expand Down
7 changes: 5 additions & 2 deletions bin/fm-crew-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@
# The run-step is AUTHORITATIVE: running/fixing -> working, ci -> working
# (the id-addressed detail read carries step words the overview does not),
# awaiting_approval/fix_review -> parked (with gate findings), terminal
# passed/checks-passed -> done, failed/cancelled -> failed. EXCEPT: while
# passed/checks-passed/passed-with-override -> done, failed/cancelled ->
# failed. passed-with-override is a passing outcome carrying an
# explicitly approved Test or CI exception (no-mistakes' own vocabulary),
# read identically to a clean passed. EXCEPT: while
# the active step is ci, `axi status` alone cannot tell "still waiting on
# checks" from "checks green, waiting on merge" (see nm_ci_checks_state) -
# a ci-step log-tail check overrides working -> done once checks read
Expand Down Expand Up @@ -1012,7 +1015,7 @@ if [ "$HAVE_RUN" = 1 ]; then

if [ -n "$outcome" ]; then
case "$outcome" in
passed) RUN_STATE="done"; RUN_DETAIL=$(passed_pr_detail) ;;
passed|passed-with-override) RUN_STATE="done"; RUN_DETAIL=$(passed_pr_detail) ;;
checks-passed) RUN_STATE="done"; RUN_DETAIL="checks green: PR ready for review" ;;
failed)
if nm_reclassify_failed_run_as_held_green; then :; else
Expand Down
2 changes: 1 addition & 1 deletion bin/fm-teardown.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1920,7 +1920,7 @@ task_status_is_terminal_run() { # <axi-status-output> <run-id>
[ "$run_id" = "$expected_id" ] || return 1
outcome=$(fm_nm_strip_quotes "$(fm_nm_field "$out" outcome)")
case "$outcome" in
cancelled|failed|passed|checks-passed) return 0 ;;
cancelled|failed|passed|checks-passed|passed-with-override) return 0 ;;
esac
return 1
}
Expand Down
3 changes: 2 additions & 1 deletion tests/fm-contributions.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,8 @@ printf '%s\n' "$*" >> "$FORGE/calls"
fault=$(cat "$FORGE/fault" 2>/dev/null || true)
case "$fault" in latency) sleep "${FORGE_LATENCY:-2}" ;; esac
case "$fault:$*" in
reserve:'api repos/o/r/'*)
# Advance once before the parallel read wave; its readers share this clock.
reserve:'api repos/o/r/issues/9')
printf '%s\n' "$(( $(cat "$FORGE/clock") + 6 ))" > "$FORGE/clock" ;;
exhaust:'api repos/o/r/issues/8/comments?'*)
printf '%s\n' "$(( $(cat "$FORGE/clock") + 100 ))" > "$FORGE/clock" ;;
Expand Down
31 changes: 31 additions & 0 deletions tests/fm-crew-state.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,20 @@ outcome: passed
EOF
}

run_passed_with_override() { # <branch>
cat <<EOF
run:
id: "01RUN"
branch: $1
status: completed
head: "${FM_FAKE_RUN_HEAD:-abc1234}"
pr: "https://github.com/o/r/pull/1"
findings: none
outcome: passed-with-override
ci_override_reason: "live checks not all passed: Lint (fail)"
EOF
}

run_passed_with_pr() { # <branch> <pr-url>
cat <<EOF
run:
Expand Down Expand Up @@ -1312,6 +1326,22 @@ test_terminal_passed() {
pass "terminal passed run is authoritative"
}

test_terminal_passed_with_override() {
reset_fakes
local d; d=$(new_case passed-with-override)
make_repo_on_branch "$d/wt" fm/feat-override
make_fakebin "$d" >/dev/null
fm_write_meta "$d/state/feat-override.meta" "window=fm:fm-feat-override" "worktree=$d/wt" "kind=ship"
FM_FAKE_AXI_STATUS="$(run_passed_with_override fm/feat-override)"
local out; out=$(run_crew_state "$d" feat-override)
assert_contains "$out" "state: done" "passed-with-override run -> done, not unknown"
assert_contains "$out" "source: run-step" "passed-with-override -> run-step source"
assert_contains "$out" "run passed: PR merged" "passed-with-override run reports merged only after the PR record says merged"
assert_not_contains "$out" "state: unknown" "passed-with-override must not fall through to unknown"
assert_not_contains "$out" "outcome: passed-with-override" "passed-with-override must not surface as a raw unmapped outcome detail"
pass "terminal passed-with-override run reads done like a clean pass"
}

test_terminal_passed_uses_matching_retirement_receipt_without_forge() {
reset_fakes
local d url read_log out
Expand Down Expand Up @@ -4883,6 +4913,7 @@ test_ci_fixing_after_green_stays_working
test_top_level_fixing_ci_running_after_green_stays_working
test_top_level_fixing_done_log_stays_working
test_terminal_passed
test_terminal_passed_with_override
test_terminal_passed_uses_matching_retirement_receipt_without_forge
test_terminal_passed_no_forge_switch_skips_read_but_keeps_receipt
test_terminal_passed_with_open_pr_does_not_claim_merged
Expand Down
27 changes: 27 additions & 0 deletions tests/fm-teardown.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2908,6 +2908,32 @@ test_parked_own_run_is_aborted_before_teardown() {
pass "a task's own parked no-mistakes run is aborted, not orphaned, before the worker is removed"
}

# An abort can race a concurrent gate response: the run finishes with a
# passing-but-not-clean outcome (an explicitly approved Test/CI exception)
# instead of landing on `cancelled`. That is still a terminal, finished run,
# so teardown must conclude cleanly rather than refuse as still-parked.
test_parked_own_run_concludes_on_passed_with_override_after_abort() {
local case_dir rc head
case_dir=$(make_case parked-run-abort-passed-with-override)
write_meta "$case_dir" no-mistakes ship
land_shippable_commit "$case_dir"
head=$(git -C "$case_dir/wt" rev-parse HEAD)

local rc=0
FM_FAKE_AXI_STATUS="$(parked_axi_status_toon fm/task-x1 "$head")" \
FM_FAKE_NM_ABORT_LOG="$case_dir/nm-abort.log" \
FM_FAKE_AXI_STATUS_AFTER_ABORT='run:
id: "01RUN"
outcome: passed-with-override
ci_override_reason: "live checks not all passed: Lint (fail)"' \
run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" || rc=$?

expect_code 0 "$rc" "parked-run-abort-passed-with-override: teardown should still succeed"
assert_no_grep "REFUSED" "$case_dir/stderr" \
"parked-run-abort-passed-with-override: a passing override outcome must not be reported as still parked"
pass "a run that lands on passed-with-override after abort is still recognized as terminal"
}

# The pipeline advanced the parked run past the submitted head in its own
# repo, so the run head object does not exist in the task copy at all and the
# strict object-local identity rule cannot bind the run. The daemon's own
Expand Down Expand Up @@ -3893,6 +3919,7 @@ test_persistent_index_lock_exhausts_retries_and_refuses_loudly
test_empty_retry_wait_uses_default_without_aborting
test_fractional_legacy_retry_wait_refuses_without_arithmetic_error
test_parked_own_run_is_aborted_before_teardown
test_parked_own_run_concludes_on_passed_with_override_after_abort
test_parked_run_advanced_past_unfetched_head_is_still_aborted
test_parked_run_with_mismatched_ledger_head_is_never_aborted
test_parked_run_with_malformed_ledger_row_is_never_aborted
Expand Down
Loading