Skip to content

fix(bin): stop reporting cancelled or zero-check CI as green - #2415

Open
localxcrm wants to merge 5 commits into
kunchenguid:mainfrom
localxcrm:fm/fm-nomistakes-cancelled-as-green
Open

fix(bin): stop reporting cancelled or zero-check CI as green#2415
localxcrm wants to merge 5 commits into
kunchenguid:mainfrom
localxcrm:fm/fm-nomistakes-cancelled-as-green

Conversation

@localxcrm

Copy link
Copy Markdown

Intent

Fix the no-mistakes CI-ready verdict so cancelled, skipped, never-run, or zero checks are never reported as green. A green verdict is valid only if at least one check completed with success AND none are pending, failed, cancelled, skipped, or never-run. Zero checks is a distinct non-success state, not success. Cover both cancelled-as-green and zero-checks-as-green. A cancelled-only run cannot produce checks-passed. A run with zero checks cannot produce checks-passed. A genuinely all-success run still produces checks-passed. Tests must prove those three cases from code, not from a comment. Do not weaken an unrelated assertion to make a suite pass. Shared firstmate material; do not push to the default branch; do not merge. Open the PR against the configured upstream and stop at green.

What Changed

  • Added bin/fm-ci-verdict-lib.sh as the single owner of the CI-ready rule: a check-conclusion set scores passed only when at least one check completed with success and none are pending, failed, cancelled, skipped, or never-run; zero checks scores empty, everything else not-passed. It is sourced as a library and also runnable as a command.
  • Reworked bin/fm-crew-state.sh to route every green claim through that scorer: the ci-log marker is now classified into a conclusion (including new cancelled/CI failures/waiting for checks/repository declares no CI phrases) before scoring, a terminal checks-passed outcome reports blocked without scored green evidence and unknown when the ci log is unreadable, and a bare completed run with no outcome — from axi status or the coarse runs list — now reads unknown instead of done, which also removes the coarse early-emit that skipped scoring entirely.
  • Added tests/fm-ci-verdict-lib.test.sh and extended tests/fm-crew-state.test.sh to pin the cancelled-only, zero-checks, and genuinely all-success cases (each new guard verified to fail against the base commit); registered the new test file in bin/fm-test-run.sh and updated AGENTS.md, docs/architecture.md, and docs/scripts.md to state the scored verdict and the outcomeless-run states. Review flagged two accepted boundaries (a CI-less repo now stays working rather than done, and the no-run status-log fallback still promotes a crew's own checks green line); the pre-existing tests/fm-watch-triage.test.sh flake reproduces on the base tree and is untouched by this change.

Risk Assessment

✅ Low: Both accepted fixes are correct and covered by tests that fail on regression, no existing fixture or consumer regresses, and every run-attributed path to done now requires scored evidence or a merged outcome.

Testing

Ran the two targeted suites (tests/fm-ci-verdict-lib.test.sh, tests/fm-crew-state.test.sh) — both pass — then produced the real evidence by driving the actual bin/fm-crew-state.sh CLI over throwaway git worktrees with a fake no-mistakes backend, side by side on base 1238402 and target 9eb7125: a checks-passed run over cancelled checks and one over zero checks both used to print state: done · checks green: PR ready for review and now print state: blocked · run reported checks-passed without green check evidence, while the genuinely all-success control still prints state: done · checks green and a terminal completed run with no outcome now reads unknown instead of done. To show the tests pin this from code rather than from a comment, I grafted the target test file onto the base tree and ran each new case in isolation: all of them fail on base and pass on target, and the all-success control passes on both, so no assertion was weakened. I also captured a transcript of the shared scorer's public command across success/cancelled/skipped/pending/failed/never-run/zero-check inputs, confirmed the runner coverage guard still passes, and exercised the downstream crew-state consumers — fm-inactive-reconcile passes, and fm-watch-triage fails non-deterministically with a different case each run on both base and target (it stubs the crew-state reader, so this change cannot reach it), which I recorded as a pre-existing environmental flake rather than a regression. This is a CLI-only change with no rendered UI surface, so the evidence is CLI transcripts rather than screenshots. Scratch trees were removed and the worktree is clean.

Evidence: Before/after CLI transcript: fm-crew-state.sh verdict on base vs target

==== BASE 1238402 (before the fix) ==== # 1. Run claims outcome: checks-passed, but every CI check was CANCELLED $ fm-crew-state.sh cancelled-only state: done · source: run-step · checks green: PR ready for review # 2. Run claims outcome: checks-passed, but ZERO checks ever ran $ fm-crew-state.sh zero-checks state: done · source: run-step · checks green: PR ready for review # 3. Genuinely all-success run (control: green must still be green) $ fm-crew-state.sh all-success state: done · source: run-step · checks green: PR ready for review # 4. Terminal completed run carrying no outcome at all $ fm-crew-state.sh no-outcome state: done · source: run-step · run completed ==== TARGET 9eb7125 (with the fix) ==== # 1. Run claims outcome: checks-passed, but every CI check was CANCELLED $ fm-crew-state.sh cancelled-only state: blocked · source: run-step · run reported checks-passed without green check evidence # 2. Run claims outcome: checks-passed, but ZERO checks ever ran $ fm-crew-state.sh zero-checks state: blocked · source: run-step · run reported checks-passed without green check evidence # 3. Genuinely all-success run (control: green must still be green) $ fm-crew-state.sh all-success state: done · source: run-step · checks green: PR ready for review # 4. Terminal completed run carrying no outcome at all $ fm-crew-state.sh no-outcome state: unknown · source: run-step · run completed without an outcome


==== BASE 1238402 (before the fix) ====
--------------------------------------------------------------------
# 1. Run claims outcome: checks-passed, but every CI check was CANCELLED
$ no-mistakes axi logs --step ci   ->  "CI checks were cancelled without reporting a verdict"
$ fm-crew-state.sh cancelled-only
state: done · source: run-step · checks green: PR ready for review
--------------------------------------------------------------------

# 2. Run claims outcome: checks-passed, but ZERO checks ever ran
$ no-mistakes axi logs --step ci   ->  "no CI checks reported - still monitoring until merged or closed"
$ fm-crew-state.sh zero-checks
state: done · source: run-step · checks green: PR ready for review
--------------------------------------------------------------------

# 3. Genuinely all-success run (control: green must still be green)
$ no-mistakes axi logs --step ci   ->  "all CI checks passed - still monitoring until merged or closed"
$ fm-crew-state.sh all-success
state: done · source: run-step · checks green: PR ready for review
--------------------------------------------------------------------

# 4. Terminal `completed` run carrying no outcome at all
$ fm-crew-state.sh no-outcome
state: done · source: run-step · run completed
--------------------------------------------------------------------

==== TARGET 9eb7125 (with the fix) ====
--------------------------------------------------------------------
# 1. Run claims outcome: checks-passed, but every CI check was CANCELLED
$ no-mistakes axi logs --step ci   ->  "CI checks were cancelled without reporting a verdict"
$ fm-crew-state.sh cancelled-only
state: blocked · source: run-step · run reported checks-passed without green check evidence
--------------------------------------------------------------------

# 2. Run claims outcome: checks-passed, but ZERO checks ever ran
$ no-mistakes axi logs --step ci   ->  "no CI checks reported - still monitoring until merged or closed"
$ fm-crew-state.sh zero-checks
state: blocked · source: run-step · run reported checks-passed without green check evidence
--------------------------------------------------------------------

# 3. Genuinely all-success run (control: green must still be green)
$ no-mistakes axi logs --step ci   ->  "all CI checks passed - still monitoring until merged or closed"
$ fm-crew-state.sh all-success
state: done · source: run-step · checks green: PR ready for review
--------------------------------------------------------------------

# 4. Terminal `completed` run carrying no outcome at all
$ fm-crew-state.sh no-outcome
state: unknown · source: run-step · run completed without an outcome
--------------------------------------------------------------------
Evidence: New tests fail against base code (proves the fix from behavior)

# Target tests/fm-crew-state.test.sh grafted onto the BASE 1238402 tree, each case run in isolation. # "not ok" on base + "ok" on target = the test pins the fix from code. vs BASE test_checks_passed_cancelled_only_is_not_done not ok - checks-passed over cancelled checks must not be done (unexpected: 'state: done') vs BASE test_checks_passed_without_green_evidence_is_not_done not ok - checks-passed with zero checks must not be done (unexpected: 'state: done') vs BASE test_checks_passed_with_success_evidence_is_done ok - a checks-passed claim corroborated by success evidence is done <-- control, green not weakened vs BASE test_completed_without_outcome_is_not_done not ok - a completed run with no outcome must not be done (unexpected: 'state: done') vs BASE test_coarse_completed_row_is_not_done not ok - coarse completed without check evidence must not be done (unexpected: 'state: done') vs BASE test_ci_monitoring_no_checks_terminal_is_not_green not ok - terminal no-checks ci-monitor run -> working (missing: 'state: working') vs BASE test_ci_ready_done_log_without_evidence_stays_working not ok - uncorroborated ci-ready status log stays working (missing: 'state: working') vs BASE test_prefixed_red_marker_after_green_is_not_green not ok - a later prefixed cancelled marker overrides the earlier green (missing: 'state: working') vs BASE test_ci_failures_phrase_after_green_is_not_green not ok - a later CI failures line overrides the earlier green (missing: 'state: working')

# Each new/changed test, run in isolation against BASE 1238402 bin/ (target test file grafted onto the base tree).
# "not ok" here + "ok" on target = the test pins the fix from code, not from a comment.

vs BASE  test_checks_passed_cancelled_only_is_not_done            not ok - checks-passed over cancelled checks must not be done (unexpected: 'state: done')
vs BASE  test_checks_passed_without_green_evidence_is_not_done    not ok - checks-passed with zero checks must not be done (unexpected: 'state: done')
vs BASE  test_checks_passed_with_success_evidence_is_done         ok - a checks-passed claim corroborated by success evidence is done
vs BASE  test_completed_without_outcome_is_not_done               not ok - a completed run with no outcome must not be done (unexpected: 'state: done')
vs BASE  test_coarse_completed_row_is_not_done                    not ok - coarse completed without check evidence must not be done (unexpected: 'state: done')
vs BASE  test_ci_monitoring_no_checks_terminal_is_not_green       not ok - terminal no-checks ci-monitor run -> working (missing: 'state: working')
vs BASE  test_ci_monitoring_cancelled_only_is_not_green           ok - cancelled-only ci-monitor marker is not green
vs BASE  test_ci_ready_done_log_without_evidence_stays_working    not ok - uncorroborated ci-ready status log stays working (missing: 'state: working')
vs BASE  test_prefixed_red_marker_after_green_is_not_green        not ok - a later prefixed cancelled marker overrides the earlier green (missing: 'state: working')
vs BASE  test_ci_failures_phrase_after_green_is_not_green         not ok - a later CI failures line overrides the earlier green (missing: 'state: working')
Evidence: Shared CI-ready scorer driven through its public command interface

# A green verdict requires >=1 completed success AND zero pending/failed/cancelled/skipped/never-run. $ bin/fm-ci-verdict-lib.sh (no checks at all) -> empty $ bin/fm-ci-verdict-lib.sh SUCCESS -> passed $ bin/fm-ci-verdict-lib.sh SUCCESS SUCCESS SUCCESS -> passed $ bin/fm-ci-verdict-lib.sh CANCELLED -> not-passed $ bin/fm-ci-verdict-lib.sh SUCCESS CANCELLED -> not-passed $ bin/fm-ci-verdict-lib.sh SKIPPED -> not-passed $ bin/fm-ci-verdict-lib.sh SUCCESS SKIPPED -> not-passed $ bin/fm-ci-verdict-lib.sh PENDING -> not-passed $ bin/fm-ci-verdict-lib.sh SUCCESS QUEUED -> not-passed $ bin/fm-ci-verdict-lib.sh FAILURE -> not-passed $ bin/fm-ci-verdict-lib.sh never-run -> not-passed $ bin/fm-ci-verdict-lib.sh SUCCESS never-run -> not-passed $ printf "SUCCESS\nSUCCESS\n" | bin/fm-ci-verdict-lib.sh -> passed $ printf "SUCCESS\nCANCELLED\n" | bin/fm-ci-verdict-lib.sh -> not-passed $ printf "\n\n" | bin/fm-ci-verdict-lib.sh -> empty

# The shared CI-ready scorer, driven through its public command interface.
# usage: bin/fm-ci-verdict-lib.sh [conclusion...]  (stdin, one per line, when no args)
# A green verdict requires >=1 completed success AND zero pending/failed/cancelled/skipped/never-run.

$ bin/fm-ci-verdict-lib.sh (no checks at all)             -> empty
$ bin/fm-ci-verdict-lib.sh SUCCESS                        -> passed
$ bin/fm-ci-verdict-lib.sh SUCCESS SUCCESS SUCCESS        -> passed
$ bin/fm-ci-verdict-lib.sh CANCELLED                      -> not-passed
$ bin/fm-ci-verdict-lib.sh SUCCESS CANCELLED              -> not-passed
$ bin/fm-ci-verdict-lib.sh SKIPPED                        -> not-passed
$ bin/fm-ci-verdict-lib.sh SUCCESS SKIPPED                -> not-passed
$ bin/fm-ci-verdict-lib.sh PENDING                        -> not-passed
$ bin/fm-ci-verdict-lib.sh SUCCESS QUEUED                 -> not-passed
$ bin/fm-ci-verdict-lib.sh FAILURE                        -> not-passed
$ bin/fm-ci-verdict-lib.sh never-run                      -> not-passed
$ bin/fm-ci-verdict-lib.sh SUCCESS never-run              -> not-passed

$ printf "SUCCESS\nSUCCESS\n" | bin/fm-ci-verdict-lib.sh   -> passed
$ printf "SUCCESS\nCANCELLED\n" | bin/fm-ci-verdict-lib.sh -> not-passed
$ printf "\n\n" | bin/fm-ci-verdict-lib.sh                  -> empty
Evidence: Targeted suite result
$ bin/fm-test-run.sh tests/fm-ci-verdict-lib.test.sh tests/fm-crew-state.test.sh
ok - terminal no-checks ci-monitor marker is not green
ok - cancelled-only ci-monitor marker is not green
ok - prefixed red marker after green is not green
ok - CI failures marker after green is not green
ok - a checks-passed claim with zero check evidence is not done
ok - a checks-passed claim over cancelled-only checks is not done
ok - a checks-passed claim corroborated by success evidence is done
ok - a completed run with no outcome is not done
ok - coarse completed row does not report done
FM_TEST_SUMMARY total=2 failed=0 skipped_gate=0 duration_ms=106011
Evidence: fm-watch-triage flake characterization (fails on base and target alike)
# tests/fm-watch-triage.test.sh is timing-sensitive (spawns a real background
# watcher and polls for exit/queue effects). It fails NON-DETERMINISTICALLY in
# this sandbox on BOTH the base and the target tree, with a DIFFERENT case each
# run - the signature of an environmental timing flake, not a regression.
#
# It also cannot be affected by this change: every case stubs the crew-state
# reader via FM_CREW_STATE_BIN/FM_FAKE_CREW_STATE, so the real verdict code
# under test here never runs inside it.

TARGET 9eb7125, tests/fm-watch-triage.test.sh, three separate runs:
  run 1: not ok - provably-working signal did not advance its .seen-* suppressor
  run 2: not ok - watcher did not surface a working: note whose crew has no running pipeline and an idle pane
  run 3: not ok - watcher did not surface a turn-end whose crew is not provably working

BASE 1238402, tests/fm-watch-triage.test.sh, two separate runs:
  run 1: stopped after 9 ok (different case)
  run 2: not ok - watcher did not exit for an actionable needs-decision signal

The one case that overlapped with the target failures, run in isolation on the
target 3/3 times:
  ok - a no-verb signal whose crew is provably working is absorbed (no exit, no queue, suppressor advanced, beacon present)
- Outcome: ⚠️ 1 warning across 2 runs (56m24s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ bin/fm-crew-state.sh:313 - The marker selector grep and nm_ci_marker_conclusion no longer cover the same phrases: the classifier recognizes "CI failures" (line 300) and "waiting for checks" (line 301), but neither appears in the grep -E alternation on line 313 that picks the marker. Because selection is tail -1 of grep-matching lines, a red line using only those phrases is invisible to the selector and an EARLIER green line wins. Concrete failing sequence: ci.log tail = ["all CI checks passed - still monitoring until merged or closed", "CI failures: 2 jobs red"] -> marker = the green line -> conclusion SUCCESS -> fm_ci_ready_verdict passed -> nm_ci_checks_state green -> state: done while CI is red. This is the same false-green class the change exists to prevent, and the drift is introduced here (the pre-change grep and case lists had identical coverage). Fix: keep the two lists in sync - add "CI failures" and "waiting for checks" to the grep alternation, or drop them from the classifier if the CLI never emits them.
  • ⚠️ bin/fm-crew-state.sh:451 - The coarse attribution path maps runs-list status completed straight to RUN_STATE=done ("run completed") without consulting the new scorer, so the authorized failure remains reachable through a sibling path in the same function. Concrete sequence: crew's own run ends with outcome=checks-passed where every check was cancelled (or there were zero checks); axi status answers with another crew's run (the exact condition the coarse fallback exists for, modelled by tests/fm-crew-state.test.sh:799); no-mistakes runs shows a head-matching row completed fm/&lt;branch&gt; &lt;sha&gt; -> RUN_SOURCE=coarse -> state: done. That contradicts the invariant this change just documented at AGENTS.md:349 ("a checks-passed claim is done only when bin/fm-crew-state.sh scores at least one successful check"). The coarse path cannot score (RUN_OUT holds another run's id), so the earliest supported boundary is the coarse status mapping itself: completed would have to become unknown/blocked rather than done. That trades false-green for false-unknown on every legitimately merged run, so it is a product-behavior decision rather than a mechanical fix.
  • ⚠️ bin/fm-crew-state.sh:299 - Five patterns in nm_ci_marker_conclusion are missing their leading *: &#34;CI checks were cancelled&#34;* and &#34;cancelled without&#34;* (line 299), &#34;CI failures&#34;* and &#34;issues detected&#34;* (line 300), &#34;waiting for checks&#34;* (line 301), &#34;repository declares no CI&#34;* (line 302). They only match when the phrase starts the line, so any real log line carrying a timestamp or indent prefix (e.g. "[12:00:04] CI checks were cancelled without reporting a verdict") falls through every arm to the empty conclusion. The mis-scoring is currently masked because nm_ci_checks_state collapses empty and not-passed to not-ready, but the lib deliberately keeps them as distinct verdicts, so the bug becomes live the moment a caller distinguishes them - and the new tests pass only because FM_FAKE_CI_LOGS fakes are unprefixed. Add the leading * to each.
  • ⚠️ tests/fm-crew-state.test.sh:813 - test_coarse_run_does_not_probe_other_branch_ci_log_for_ready_status still names the property "coarse run does not probe another branch's ci log", but with the coarse early-emit removed the assertions no longer distinguish probing from not-probing: FM_FAKE_CI_LOGS is a pending marker, so a regression that DID probe the other branch's log would read not-ready and still emit state: working, and the test would pass. The guard for the original cross-branch bug is now vacuous. Restore it by setting FM_FAKE_CI_LOGS to the green marker ("all CI checks passed - still monitoring until merged or closed"): current code skips the block for coarse and stays working, while any code that probed the other branch's log would emit done and fail the existing assert_not_contains "state: done".
  • ℹ️ bin/fm-bearings-snapshot.sh:246 - bin/fm-ci-verdict-lib.sh declares itself the "ONE owner" of the green-verdict rule, but fm-bearings-snapshot.sh keeps an independent jq scorer over statusCheckRollup that reaches a different answer for conclusions the intent names as never-green: a check with status COMPLETED and conclusion SKIPPED (or NEUTRAL/STALE) is not in the failing list and is not pending, so an all-skipped rollup renders as checks "passing" in the captain's PR snapshot. This is pre-existing and untouched by the diff, and it scores GitHub rollup data rather than a no-mistakes run verdict, so it may be deliberately outside the stated scope ("the no-mistakes CI-ready verdict") - flagging it as the one immediately competing semantic owner rather than proposing a redesign.
  • ℹ️ bin/fm-ci-verdict-lib.sh:32 - fm_ci_ready_verdict duplicates the identical trim/success/short-circuit body across the args branch and the stdin branch, so the scoring rule now has two copies to keep in sync inside its own single-owner file. It collapses to one loop by normalizing the input first, e.g. if [ &#34;$#&#34; -eq 0 ]; then set -- $(cat); fi (or reading stdin into a positional list) and then running the single loop over "$@". Non-functional cleanup; behavior is unchanged.
  • ℹ️ bin/fm-crew-state.sh:302 - Consequence worth stating explicitly, not a defect: "no CI checks reported - still monitoring until merged or closed" used to score green, and now scores empty -> not-ready. On a repo that genuinely configures no CI, a crew therefore never reaches done from the ci-monitor path (it stays working until the PR merges, and a terminal checks-passed run reads blocked). That is exactly what the intent requires ("Zero checks is a distinct non-success state, not success"), so it is correct as specified - but it changes steady-state fleet surfacing for CI-less repos, since crew_absorb_class maps blocked/unknown to "none" and those wakes will surface to the captain instead of being absorbed.

🔧 Fix: sync CI marker patterns and fail coarse completed closed
4 issues (1 warning, 3 infos) still open:

  • ⚠️ tests/fm-ci-verdict-lib.test.sh:36 - out=$(score) invokes bin/fm-ci-verdict-lib.sh with zero arguments, which by contract enters the stdin branch (while IFS= read -r raw). Command substitution does not redirect stdin, and bin/fm-test-run.sh:1549 runs serial scripts as bash &#34;$script&#34; 2&gt;&amp;1 | tee &#34;$out&#34; with JOBS=1 by default, so the test inherits the invoker's stdin. Run from a terminal (or any runner whose stdin is an open pipe), read blocks and the whole suite hangs at this line instead of asserting. It only appears to work when stdin happens to be /dev/null or already at EOF. Fix: out=$(score &lt; /dev/null) - the zero-checks assertion is unchanged (empty stdin still yields empty) and the hang is removed. The sibling blank-line case on line 39 already pipes its input and is fine.
  • ℹ️ bin/fm-crew-state.sh:513 - Sibling path check after closing the coarse hole: on the full path, when axi status reports status: completed with NO outcome: field, the no-outcome case arm still maps it to RUN_STATE=done / "run completed" without consulting the scorer - the same shape (done with zero check evidence) that COARSE_STATUS=completed was just changed to reject at line 456. I could not prove from source that the CLI ever emits a completed run without an outcome, so I am not claiming reachability; the supporting signal is bin/fm-teardown.sh:1251, which treats the OUTCOME (cancelled|failed|passed|checks-passed), not the status word, as the terminal marker - implying a completed row lacking an outcome is a shape the codebase already guards against elsewhere. If it is reachable, mapping it to unknown would match the coarse decision; if the CLI guarantees an outcome on every completed run, this arm is dead and can stay as is. Raising it as a question rather than a fix because it is the same product-behavior tradeoff (false-green vs false-unknown) already decided once for the coarse path.
  • ℹ️ bin/fm-crew-state.sh:456 - Downstream consequence of the accepted coarse completed -> unknown mapping, recorded rather than proposed for change: bin/fm-inactive-reconcile.sh:341-345 only creates a durable terminal-outcome record (and supervisor wake) for state: done or state: failed , so an inactive crew whose finished run is attributed only coarsely now produces no terminal record where it previously produced a done record. The crew is still surfaced through the ordinary wake path (crew_absorb_class maps unknown to "none", so its wakes are not absorbed), and the coarse fallback is transient - a later scan where axi status returns the crew's own run takes the full path and scores properly. Fail-closed and consistent with the invariant the user chose; noting it because it was not visible when that mapping was decided.
  • ℹ️ tests/fm-crew-state.test.sh:549 - test_prefixed_red_marker_after_green_is_not_green asserts a genuine behavior (a later prefixed cancelled line must beat an earlier green one) but cannot fail if the leading * globs are removed again: with the anchored patterns, "[12:00:04] CI checks were cancelled without reporting a verdict" matches no case arm and yields the empty conclusion -> verdict empty, and nm_ci_checks_state collapses empty and not-passed to the same not-ready, so the crew reads working either way. No crew-state-level test can distinguish them while that collapse exists; only a direct assertion on the classifier could. The companion test_ci_failures_phrase_after_green_is_not_green does not have this problem - it fails on a regression of the grep list. No action needed unless a caller ever distinguishes empty from not-passed.

🔧 Fix: reject outcomeless completed run and unblock verdict test stdin
1 info still open:

  • ℹ️ bin/fm-crew-state.sh:606 - Boundary note on the durable fix, no action recommended. After this round every run-attributed route to done requires evidence (line 475 outcome=passed/merged, 480 checks-passed + scored green, 528 ci-step + scored green, 550 status-log + scored green, and both bare-completed shapes now read unknown at 456 and 516). The one remaining way an unscored "checks green" claim still becomes done is the no-run fallback here: when NO run is attributable at all - worktree HEAD rewritten past the run head, or the run aged out of no-mistakes runs --limit - and the pane is exact-idle, map_log_state promotes the crew's own done: PR ... checks green line to state done with source status-log. That is reachable after a cancelled-CI run whose crew already wrote that line, but crew-state has literally no run to score at that point, and step 4 of this file's header documents the fallback as intentional. There is no earlier shared boundary that would restore the invariant without removing the fallback itself, so this is the honest edge of the fix rather than a defect to patch.
⚠️ **Test** - 1 warning
  • ⚠️ tests/fm-crew-state.test.sh:615 - The intent requires "a cancelled-only run cannot produce checks-passed" be proven from code, but no test pinned it. test_ci_monitoring_cancelled_only_is_not_green passes against the base bin too (on base, "CI checks were cancelled" was an unrecognized marker, so it scored unknown and nothing overrode working) — it was green for the wrong reason and guarded nothing. The outcome: checks-passed + cancelled-checks path, where base actually reported state: done · checks green, had no coverage at all. Fixed by adding test_checks_passed_cancelled_only_is_not_done, verified to fail against base 1238402 and pass on the target.
  • bash tests/fm-ci-verdict-lib.test.sh — 4 scoring cases (cancelled, zero-checks, all-success, skipped/pending/failed/never-run), all pass
  • bash tests/fm-crew-state.test.sh — full suite passes including all CI-verdict cases and the added test_checks_passed_cancelled_only_is_not_done
  • Added test_checks_passed_cancelled_only_is_not_done to tests/fm-crew-state.test.sh pinning outcome: checks-passed + cancelled ci.log → not done
  • Manual end-to-end CLI drive of bin/fm-crew-state.sh &lt;id&gt; over 7 scenarios against a real throwaway git worktree + faked no-mistakes axi status/axi logs/runs, run against BOTH base 1238402 and target f7040c7 (bash drive-crew-state.sh &lt;repo&gt; &lt;label&gt;)
  • Per-test regression proof: each new guard test run individually against the base bin/ (FM_ONE_TEST=&lt;test&gt; bash tests/one.sh) — confirmed 8 of them fail on the old behavior, which is how the untested cancelled case was found
  • Mutation testing of bin/fm-ci-verdict-lib.sh: (M1) empty→passed, (M2) CANCELLED treated as success, (M3) all-success→not-passed; each mutation caught by both suites
  • bash drive-scorer.sh &lt;repo&gt; — direct CLI transcript of bin/fm-ci-verdict-lib.sh over 25 arg/stdin conclusion sets
  • bin/fm-test-run.sh --list --changed --base 12384026 — confirms tests/fm-ci-verdict-lib.test.sh and tests/fm-crew-state.test.sh are auto-selected from the changed paths
  • bash tests/fm-test-run.test.sh — exit 0, confirms the new test file's family registration is consistent
  • bash tests/fm-documentation-audiences.test.sh, bash tests/fm-supervision-instructions.test.sh, bash tests/fm-ensure-agents-md.test.sh — doc-contract suites owning the edited AGENTS.md / docs/ files, all exit 0

🔧 Fix: verify cancelled checks-passed guard; watcher flake is environmental
1 warning still open:

  • ⚠️ tests/fm-watch-triage.test.sh:393 - tests/fm-watch-triage.test.sh fails non-deterministically in this sandbox, with a DIFFERENT case each run, on both the base tree (1238402) and the target (9eb7125). Target runs failed on 'provably-working signal did not advance its .seen-* suppressor', then 'watcher did not surface a working: note whose crew has no running pipeline and an idle pane', then 'watcher did not surface a turn-end whose crew is not provably working'; base runs failed on a fourth and fifth distinct case. The suite spawns a real background watcher and polls for exit/queue side effects, so it is timing-sensitive. It also cannot be affected by this change: every case stubs the crew-state reader through FM_CREW_STATE_BIN/FM_FAKE_CREW_STATE, so the real verdict code never runs inside it. The one overlapping case passed 3/3 when run in isolation on the target. Informational: pre-existing environmental flake, not a regression from this change, and not fixed here because it is out of this change's scope.
  • bin/fm-test-run.sh tests/fm-ci-verdict-lib.test.sh tests/fm-crew-state.test.sh — both suites pass, 0 failures
  • Manual end-to-end CLI transcript: real bin/fm-crew-state.sh &lt;id&gt; driven over throwaway git repos with a fake no-mistakes serving axi status / axi logs, run against base 1238402 bin/ and target 9eb7125 bin/, for cancelled-only, zero-checks, all-success, and no-outcome scenarios
  • Per-test base comparison: target tests/fm-crew-state.test.sh grafted onto a base 1238402 tree, each case run in isolation — test_checks_passed_cancelled_only_is_not_done, test_checks_passed_without_green_evidence_is_not_done, test_completed_without_outcome_is_not_done, test_coarse_completed_row_is_not_done, test_ci_monitoring_no_checks_terminal_is_not_green, test_ci_ready_done_log_without_evidence_stays_working, test_prefixed_red_marker_after_green_is_not_green, test_ci_failures_phrase_after_green_is_not_green all fail on base
  • Control check: test_checks_passed_with_success_evidence_is_done passes on both base and target (green was not weakened)
  • Manual scorer transcript: bin/fm-ci-verdict-lib.sh public command driven over success / cancelled / skipped / pending / failed / never-run / zero-check inputs, via both argv and stdin
  • bin/fm-test-run.sh --check-coverage — FM_TEST_COVERAGE ok total=147 (new test file is registered in a family/lane)
  • bin/fm-test-run.sh tests/fm-watch-triage.test.sh tests/fm-inactive-reconcile.test.sh — downstream crew-state consumers; inactive-reconcile passes, watch-triage flakes on base and target alike
  • bash tests/fm-watch-triage.test.sh repeated on target and on the base tree to characterize the flake
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

A checks-passed claim is now done only when at least one check completed
with success and none are pending, failed, cancelled, skipped, or
never-run. Zero checks is a distinct non-success state.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c32b24b20

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bin/fm-crew-state.sh
*"CI check cancelled"*|*"CI checks were cancelled"*|*"cancelled without"*) printf 'CANCELLED' ;;
*"checks failed"*|*"CI failures"*|*"issues detected"*) printf 'FAILURE' ;;
*"CI checks running"*|*"waiting for checks"*|*"base branch advanced"*"re-arming CI monitor timeout"*) printf 'PENDING' ;;
*"no CI checks reported"*|*"repository declares no CI"*) ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve trusted no_ci checks-passed verdicts

Captain, for repos using no-mistakes' trusted no_ci: true mode, the upstream skill says checks-passed is valid when that declaration covers a zero-check repo (source), and this branch even selects repository declares no CI; mapping it to an empty conclusion makes fm_ci_ready_verdict return empty/not-ready, so a legitimate checks-passed run is reported blocked (or stays working in the CI monitor) and the PR is never surfaced as ready. Please distinguish the trusted declaration from a generic no CI checks reported line and score it as the accepted success case.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant