fix(bin): prefer a live no-mistakes run over a terminal one - #2881
Conversation
54a412b to
e9306d0
Compare
|
Speaking as Kun's firstmate: First look on HEAD Contract-class: restore. Selection defect in shared no-mistakes run matching: when multiple recorded runs bind to the same worktree/branch identity, the unconfigured path could prefer a terminal corpse over a live run, misreporting a healthy task as VISION.md (each rule)
Attestation: MISMATCH — body marker still Waiting on the author: raise/refresh via |
A worktree can bind to more than one recorded no-mistakes run at once. The branch-and-code-identity rule in bin/fm-nm-run-lib.sh accepts both an exact-equal commit and a worktree-is-an-ancestor match, but never stated which wins when both bind, so the tie fell to whichever candidate the caller reached first. Observed on a live fleet: a crashed validation daemon left a FAILED run at the worktree's own commit while the live run that replaced it validated a descendant commit on the same branch. Bare `axi status` answers with the most-recently-touched run - the corpse - and it bound by the equal-commit rule, so every recomputation reported `failed` for a task whose real run was healthy. The same label had also read `failed` earlier while the work was genuinely stalled, so the signal was wrong in both directions. State the live-over-terminal policy in the matching rule's own contract, where the equal-commit and ancestor rules already live, and add fm_nm_run_status_class as the one classifier that decides liveness from a recorded status word. fm-crew-state.sh applies it on both selection paths: the runs listing now scans past a terminal row for a live one, and a terminal `axi status` answer is provisional until the listing has been asked whether this worktree also has a live run. Same-liveness-class candidates keep the listing's newest-first precedence, and a status word the classifier cannot place keeps the caller's own ordering rather than displacing a known result, so a single-run task and a task whose runs are all terminal are unchanged. Regression coverage reproduces the proven case (terminal run at the worktree's exact commit plus a live run descending from it) and its runs-list twin; both fail under the old tie-break. Two companion cases pin the no-widening half - two terminal rows still resolve newest-first, and a terminal run with no live sibling keeps its full run-step detail - and both pass before and after the change.
The FM_CREW_STATE_RUNS_LIMIT comment in bin/fm-crew-state.sh still described the runs ledger as scanned only by the cross-branch fallback, but the live-over-terminal fix also consults it as the live-sibling probe behind a terminal axi status answer. Point the comment at docs/configuration.md as the setting's owner instead of restating a second copy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
e9306d0 to
eee895f
Compare
|
Speaking as Kun's firstmate: this is merged. Thank you @jayjongcheolpark — really appreciate you taking the time on this. |
Intent
The captain reports that PR #2881 ("fix(bin): prefer a live no-mistakes run over a terminal one", branch
fm/crewstate-live-run-preference, head 54a412b, CI green 14/14) now has MERGE CONFLICTS with main. Captain's words: "2881 은 머지 컨플릭이 있네요" - rebase it onto current origin/main so it can be merged.What 2881 delivers (all of it must survive the rebase): when a task's worktree binds to more than one recorded no-mistakes run,
bin/fm-crew-state.sh(via the branch-and-code-identity matching rule inbin/fm-nm-run-lib.sh) must prefer a LIVE (non-terminal) run over a terminal one; among candidates of the same liveness class the existing precedence is unchanged; the policy is stated once in the matching rule's documented contract; 4 regression tests intests/fm-crew-state.test.shreproduce the proven case (terminal run at the worktree's exact commit plus a live run descending from it); no behavior change for single-run or terminal-only cases; docs in docs/architecture.md and docs/configuration.md describe the rule.Conflicting files against origin/main as of dispatch: bin/fm-crew-state.sh, bin/fm-nm-run-lib.sh, docs/architecture.md, docs/configuration.md, tests/fm-crew-state.test.sh.
What Changed
bin/fm-nm-run-lib.sh:fm_nm_runs_status_for_worktreeno longer returns on the newest binding row when that row is terminal. It holds the terminal word, keeps scanning older rows for arunningrow that also binds to the worktree (by the head rule, or, when the live row's head is unfetched, only when the held terminal row sits at the worktree's exact HEAD), and prints the live word if found. Malformed rows nowbreakand print the already-decided answer instead of swallowing it. A newfm_nm_run_status_classhelper classifies ledger status words as terminal, live, or unknown, and the header comment states the live-over-terminal contract once.bin/fm-crew-state.sh: when bareaxi statusbinds this branch's run but the run is not active, the reader consults the runs ledger via the same matching rule and switches to the coarse live status only if a live sibling exists. Single-run, terminal-only, and unclassifiable cases keep their existing result and precedence.tests/fm-crew-state.test.shadds six regression tests for the crashed-run-at-exact-commit case, the runs-list path, the unfetched live sibling, and the no-widening guarantees.docs/architecture.mdanddocs/configuration.mddescribe the rule and the second ledger read behindFM_CREW_STATE_RUNS_LIMIT.Risk Assessment
✅ Low: The fix-round commit is a small, bounded extension of the ledger scan that matches the user-authorized remedy exactly, its regression test executes the real crew-state script and fails without the change, teardown's expected-head caller is unaffected because its anchor check happens before the widened branch is reachable, and all intent-required deliverables (rule, single contract statement, tests, docs) are present after the rebase.
Testing
Ran the targeted fm-crew-state and fm-teardown test files on the target commit (all pass), then proved the regression by running the six new tests and a manual crew-state transcript against both the base and target bin scripts: the healthy task reads "state: failed" before the fix and "state: working" after, in both the proven descendant-head shape and the authorized unfetched-head shape, while terminal-only and unclassifiable-row cases are unchanged. No visual artifact applies because the surface is a CLI status line.
Evidence: Before fix (base 78318e2): crew-state transcript and new tests
Source: Before fix (base 78318e2): crew-state transcript and new tests
$ no-mistakes runs --limit 200 failed fm/crewstate-live-run-preference fe8498d 2026-08-05 11:20 running fm/crewstate-live-run-preference c09d485 2026-08-05 10:05 $ bin/fm-crew-state.sh demo state: failed · source: run-step · run failed (unfetched live head 0123abc: same) state: failed · source: run-step · run failed not ok - the live run outranks the terminal corpse bound to the same worktree not ok - an older live row outranks the branch's newest terminal row not ok - an unfetched live row anchored by the exact-head terminal row outranks it ok - two terminal rows keep the existing newest-first precedence ok - an unclassifiable status row keeps the ledger's newest-first precedence ok - a terminal run with no live sibling is unchanged new-test failures: 3Evidence: After fix (target 185a780): crew-state transcript and new tests
Source: After fix (target 185a780): crew-state transcript and new tests
$ no-mistakes runs --limit 200 failed fm/crewstate-live-run-preference 9b9cd47 2026-08-05 11:20 running fm/crewstate-live-run-preference 6646b6e 2026-08-05 10:05 $ bin/fm-crew-state.sh demo state: working · source: run-step · validating (background run) (unfetched live head 0123abc: same) state: working · source: run-step · validating (background run) ok - a live run outranks a terminal run bound to the same worktree ok - runs-list selection prefers a live row over a newer terminal one ok - an unfetched live sibling outranks a terminal row at the worktree's exact commit ok - two terminal rows keep the existing newest-first precedence ok - an unclassifiable status row keeps the ledger's newest-first precedence ok - a terminal run with no live sibling is unchanged new-test failures: 0Evidence: Full tests/fm-crew-state.test.sh log on target
Source: Full tests/fm-crew-state.test.sh log on target
Evidence: Full tests/fm-teardown.test.sh log on target
Source: Full tests/fm-teardown.test.sh log on target
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-nm-run-lib.sh:241- The live-over-terminal displacement only fires when the older live row's sha resolves in the task worktree (fm_nm_head_matches_worktree at line 241). The reproduced case commits the descendant locally, so tests pass, but the lib's own header (fm_nm_run_is_pipeline_owned_active) states that a live run's lane head is routinely NOT a git object in the task copy because fix-round commits live only in the gate repo. Concrete path:axi statusreturns the failed corpse at the worktree's exact commit; ledger newest row is that corpse, older row isrunning <branch> <unfetched-sha>; the scan sets decided=failed, reaches the running row, head match fails,continue, printsfailed; crew-state then reports the healthy task as failed. The anchored pending_st path does not help because it only recognizes an unresolvable running row when it is the NEWEST branch row. The intent authorizes only the proven descendant-present case, so this is not a conformance contradiction, but the same authorized failure is reachable in the routine unfetched-head shape. The remedy (accept an unresolvable running row as the live sibling when the held terminal row sat at the worktree's exact commit, mirroring the anchor rule) widens the ledger rule's semantics, so it needs authorization rather than an auto-fix.bin/fm-crew-state.sh:590- Every crew whose boundaxi statusrun is terminal (the steady state of a finished task) now issues one additionalno-mistakes runs --limit 200call per state read to look for a live sibling. It is bounded by FM_CREW_STATE_NM_TIMEOUT and only runs for ships with a branch and a bound terminal run, so this is an accepted cost of the fix, noted for awareness.tests/fm-crew-state.test.sh:1132- The intent lists 4 regression tests; the branch carries 5 (the extra one, test_terminal_run_without_live_sibling_is_unchanged, covers the required 'no behavior change for terminal-only cases' criterion). All five execute the real fm-crew-state.sh against fake CLI output and assert emitted state, so none is a source-content-only assertion. Over-delivery, not a contradiction.🔧 Fix: accept unfetched live sibling anchored at exact worktree head
1 info still open:
bin/fm-nm-run-lib.sh:249- The authorized unfetched-head rule is implemented as specified: an unresolvablerunningrow on the same branch displaces a held terminal row only when that terminal row resolved to exactly the worktree HEAD (decided_exact at line 279), a resolvable live row still has to bind via fm_nm_head_matches_worktree (line 247), and every other exit path is unchanged (traced: terminal-only, unknown newest word, non-binding newest row, pending_st anchor, malformed rows nowbreakand print the already-decided word or nothing). One property worth knowing about, inherent in the authorized remedy rather than a defect in its implementation: unlike the pipeline-continuation anchor, which admits only the IMMEDIATELY older row, this rule accepts an unresolvablerunningrow at any depth below the terminal anchor (intervening same-branch terminal rows are skipped at line 245). Since the ledger is ordered by last touch, such a row was touched less recently than the crashed corpse, so arunningrow that a crashed daemon never finalized would keep displacing a later, legitimately failed run at the same exact commit for as long as it stays on the ledger. The user explicitly accepted this widening, so no action is requested.✅ **Test** - passed
✅ No issues found.
bash tests/fm-crew-state.test.shon target 185a780 (all 85 cases pass, including the 6 new live-over-terminal cases)bash tests/fm-teardown.test.shon target 185a780 (all 84 cases pass; fm-teardown.sh is the lib's other consumer of fm_nm_runs_status_for_worktree)Regression proof: extracted base 78318e2 viagit archive, ran the 6 new tests against the base bin scripts:test_terminal_corpse_loses_to_live_run_on_same_branch,test_runs_list_live_row_outranks_newer_terminal_row,test_unfetched_live_sibling_outranks_terminal_row_at_exact_headfail on base and pass on target;test_only_terminal_rows_keep_newest_first_precedence,test_unknown_status_row_keeps_newest_first_precedence,test_terminal_run_without_live_sibling_is_unchangedpass on bothManual end-user transcript: invokedbin/fm-crew-state.sh <id>over a real throwaway git worktree with fakeno-mistakes axi status(failed run at worktree HEAD) and fakeno-mistakes runs --limit 200(failed newest row + older running row) for both the descendant-head and unfetched-head shapes, on base and targetgit merge-base --is-ancestor origin/main 185a780confirms the branch is rebased onto current origin/main✅ **Document** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.