fix(bin): split the silent-pane readout three ways and defer the focus-blocked teardown close - #34
Merged
Merged
Conversation
A pane whose crew's own run is merely holding - validating, the pipeline's ci monitor, or a run parked at a gate - was read as `working`, so the wedge timer aged on lanes no human had touched; the same quiet surfaced as a bare stale wake on the other face of that one root cause. `crew_stale_class` (bin/fm-classify-lib.sh) now owns the three-state readout and `pause_state_class` maps it onto the watcher's decision: only a busy pane or an actively working run step ages the wedge timer, a quiet external wait and an already-landed delivery take the bounded pause cadence, and a run step the pipeline itself reports quiet - its own no-progress verdict, carried into the detail line by fm-crew-state.sh - surfaces at once. The terminal-status override uses the narrower `crew_stale_is_actively_working`, so a pipeline that is only monitoring its PR no longer restarts the timer on a delivered lane. Teardown no longer abandons a landed task whose Herdr projection pane sits on the captain's active tab: the focus gate and the close helper report why they refused, the journal is retired only against a confirmed-gone pane, and an active-tab refusal on an agentless pane finishes the record cleanup while leaving the close recorded for the session-start presentation cleanup to resume. Every other refusal, and a refusal on a pane that still holds an agent, keeps every record exactly as before; the captain's focus is never taken and the unlanded-work checks are untouched.
…l take it Review finding F5 (no-mistakes run 01M3950EJT1ZK91KJE08X4RA7J): the deferred close was licensed by "the focus gate refused and the pane has no agent", while the session-start sweep that must finish the close requires strictly more - the exact journal binding, the label/token grammar, one tab and one pane, and a lone idle childless shell. An agent-free pane that is not that shape was therefore handed a deferred close the sweep would refuse on every session start, after its records were already gone. The deferral now asks the resumer itself. `bin/fm-herdr-session-cleanup.sh --candidate-ready <task-id> [--ignore-focus] [--pending-meta]` is a read-only mode, takes no locks, and relaxes exactly the two conditions a deferral means (the active tab that caused it, and the record this teardown is about to remove). Both the sweep's close path and that check run the same proof sequence, so they cannot drift, and anything unproven keeps every record - a close nothing can ever collect is worse than a refusal. The post-agent-exit reading is decided from this repository's own contract rather than guessed: `fm_backend_herdr_pane_agent_state` names `stale-agent` (a Herdr registration that outlived its process, the crew shape) "the explicit agent-free reason", so the accepted set is `no-agent|stale-agent`. It lives once in `fm_backend_herdr_pane_agent_free`, shared by the sweep, the deferral, and the close helper's new `agent-free` requirement; what licenses closing is still the process proof that follows it, which rules out both a running agent and the nested shell `fm_backend_herdr_tab_is_husk` refuses. That husk rule is unchanged, as are the unlanded-work checks and the focus protection. Also retires the presentation journal only against a confirmed-gone pane: an agentless pane that is still present is not a closed pane, and reading its agent state as one dropped the only durable record of an open pane. Tests: the deferral is proven to be one the sweep then takes (and the sweep is run for real to close it), a `stale-agent` pane is accepted by both the deferral and the sweep while still requiring the idle-shell proof, and an agent-free pane that is not a clean idle shell keeps every record.
…ng honest Review findings F6, F2, F4 and F1 (same run as the teardown deferral fix). F6: `handle_paused_stale`'s `waiting`/`landed` arms were unreachable - every call site reaches it for a declared or inconclusive wait, and both derived-wait callers route to `handle_derived_wait_stale`. The arms, their duplicated wording, and the unused `[class]` parameter are gone, so the derived-wait absorb has one owner. F2/F4: a derived wait now keeps its cadence anchor and re-surface throttle across pane churn (only the hash-scoped half resets, exactly as the declared path protects itself), so a ticking render cannot restart the wait's clock until it never re-surfaces; and the `working` arm ends a derived chain, so a wait that ended cannot hand its finished anchor to the next one. F1: the two registered tests still asserting the superseded "active run step => wedge timer" contract are migrated - the wedge timer is exercised with a busy-pane fake, and the pause transition asserts reclassification into the derived wait, plus a third phase proving a busy pane still arms the wedge timer. F3 is answered by documentation rather than a silent behaviour change: the derived class IS re-derived on every stable stale poll, because the declared path's cheap window is also guarded by an endpoint-liveness probe every poll and reusing it would delay both a no-progress surface and the return to wedge tracking.
…de has Review finding architecture-doc-unrecognized-active-step (run 01M39BH5T8338MWQWC7SE0SSQA): the paragraph claimed every unrecognized detail "keeps surfacing exactly as before", while a `working · source: run-step` detail no version of the client vocabulary recognizes is `advancing` and takes the same bounded-cadence derived-wait absorb as `monitoring` - the very sentence a maintainer would read before adding a step detail. Only the client's own no-progress marker moves such a step out of that class. Documentation only; no behaviour changes.
…lane Ruling on the ask-user finding away-mode-still-escalates-quiet-owned-lanes (the D2 root cause on its other, already-existing consumer path, and the posture where a false escalation costs the most): the away supervisor classified a quiet lane only from its status line, so a lane whose own run was advancing, holding on its ci monitor or a gate, or already landed kept being aged into "stale persisted Ns (possible wedge)". classify_stale now asks the same single-owner readout the always-on watcher uses (bin/fm-classify-lib.sh's crew_stale_class) at both places a non-terminal quiet can reach: the transient `working:` arm and the final persistence-recheck fallback. A quiet external wait returns the daemon's existing `pause` action, so the caller records the bounded pause cadence and drops any wedge marker. The three guards hold, each pinned by tests: - a genuinely unexplained quiet still ages and escalates: a busy pane, a run step the client marks quiet (no progress), a stopped/torn-down crew, and an unreadable verdict all keep `self|transient stale` and still escalate; - an undecidable or unreadable reading is never promoted to a wait; - nothing outside the away posture changes: the terminal, declared-wait, and captain-held paths are untouched, the non-away watcher is untouched, and the teardown pre-flight checks and focus protection are untouched.
…n; correct readout doc
…derived-wait downgrade
…ing-cadence asymmetry
…e propagation The stale-pane readout, the wedge timer, and the away-mode recheck interaction spent four review rounds without settling, so the derived-wait machinery and its away-mode propagation leave this branch. What stays is the readout itself (crew_stale_class and its token contract) and the D1 teardown deferral. Reverted to main: the watcher's pause_state_class and every stale-path consumer it drives, the daemon's quiet-wait classification and derived-wait records, the derived-wait anchors and cadence, and the docs describing them.
…d-close documentation
onyx-space
force-pushed
the
fm/stale-readout-three-states-d1d2
branch
from
September 24, 2026 15:56
553ca37 to
18c2e31
Compare
…test code (both findings are in files this change touches; verified against the base diff). Root causes and fixes: 1. tests/fm-herdr-session-cleanup.test.sh (2x SC2329 "function is never invoked"): the new stale-agent sweep cases define `fm_backend_herdr_pane_agent_state` inside a subshell to override the sourced production function; ShellCheck cannot see the indirect call through `fm_herdr_session_cleanup`. Applied the repo's existing idiom in this same file (see its lines 43/49): a `# shellcheck disable=SC2329 # invoked indirectly by the sweep under test.` directive above each override. No behavior change. 2. tests/fm-watch-triage.test.sh (SC2034 `want`/`got` unused): the new `test_crew_stale_three_state_readout` declared leftover locals it never reads. Removed the unused names (`want line got`) from the `local` list. Dead declaration only; behavior unchanged. Verification (CI-parity: ShellCheck 0.11.0, `--norc --external-sources`, full dataflow, SC2329/SC2034 enabled as CI runs them): `bin/fm-lint.sh tests/fm-herdr-session-cleanup.test.sh tests/fm-watch-triage.test.sh` -> rc=0; `bin/fm-lint.sh` over all ten shell roots changed by this PR -> rc=0. Both affected test files execute successfully via `bin/fm-test-run.sh tests/fm-herdr-session-cleanup.test.sh tests/fm-watch-triage.test.sh` -> rc=0 (0 failed, 0 gate-skipped). Changes are confined to the worktree and touch only the two test files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
队长 2026-09-24 的答复(经 w 机 firstmate 转达):w 侧诊断出的 D1、D2 这两条由 h 机起 lane 做,w 只留卡跟踪。
两条的原始记录在 w 机(data/backlog.md 的 elmo-stale-d1-teardown-tab-lock、elmo-stale-d2-finished-lane-working),诊报是 w 的 data/elmo-stale-rate-diagnosis/report.md。
D1:收线(teardown)因为「队长当前聚焦的 Herdr 工作区」而拒绝关闭 pane ⇒ 已落地的 endpoint 收不干净,下一轮又被当成 wedge 复查。w 的诊报说单修这一条就能消掉样本里 40% 的 stale。
D2:已完工的 lane 仍被 pause_state_class 读成 working ⇒ wedge 计时器在成品上反复开火(样本里 w29 十七次、w2Z 七次,全是误报)。
同族的第二面(w 2026-09-15 实测):正在等门禁、或正在跑管线的 lane,其 pane 被反复报 stale(同一批 pane 多次命中:elmo-d5-microperf-core ≥3 次、elmo-sa1626-after-docs ≥2 次);当日每次都证伪成功 —— ① pgrep -x pi 后逐个 readlink /proc//cwd 命中该 lane 的工作树(agent 活着)② fm-crew-state.sh 的 source=run-step ③ no-mistakes axi status 的 phase 比上次前进 ④ 等门禁时 status=awaiting_approval。
w 诊报的原话:这是同一条根因 —— busy/stale 读数没有区分「忙」与「静着等外部(门禁/管线)」;D2 是「静着等」被读成 working,同族那面是「静着等」被读成 stale。它建议一起修:一个读数、三种状态(忙 / 静着等 / 真死)。
队长给的边界(w 转达的原话):这两条只改读数与收线语义,不许放松任何安全边界;teardown 的 unlanded-work 检查、焦点保护都必须保留,只是要能正确区分状态。
【本次本地 test 步(务必 intent-targeted,勿跑全套)】CONTRIBUTING.md(line 73-74)规定本地 no-mistakes Test 保持 intent-targeted、不配置 commands.test;请按本改动涉及的面跑,例如 tests/fm-watch-triage.test.sh、tests/fm-daemon.test.sh、tests/fm-teardown.test.sh、tests/fm-herdr-session-cleanup.test.sh、tests/fm-crew-state.test.sh、tests/fm-backend-herdr.test.sh(经 bin/fm-test-run.sh)。不要跑 bin/fm-test-run.sh --all(实测 >31 分钟,会撞 test 步 30 分钟硬顶)。
【2026-09-24 收敛后的范围(firstmate 裁决 013/014/015)】本 PR 只交两件事:① 三态读数本体 —— bin/fm-classify-lib.sh 的 crew_stale_class 与 crew_stale_is_actively_working(六态:busy/advancing/monitoring/landed/paused/none)加 bin/fm-crew-state.sh 的
no recent step activity标记,含其测试与文档;② D1 收线递延 —— bin/fm-teardown.sh 在「焦点被拒」时仅当 bin/fm-herdr-session-cleanup.sh --candidate-ready 自证会接手该候选时才递延,bin/backends/herdr.sh 报告拒绝原因并提供 agent-free 判据;含其测试与文档。watcher 的 stale/wedge 消费方(pause_state_class 及其所有分支)与 away 姿态(bin/fm-supervise-daemon.sh)已按裁决回到 main 原样,本 PR 不改其行为;静着等的路由/复核退化作为已知缺口记录在案,不在本 PR 补。本地 test 步请保持 intent-targeted:bin/fm-test-run.sh 跑 tests/fm-watch-triage.test.sh tests/fm-crew-state.test.sh tests/fm-teardown.test.sh tests/fm-herdr-session-cleanup.test.sh tests/fm-backend-herdr.test.sh tests/fm-daemon.test.sh,不要跑 --all(全套 >31 分钟会撞 30 分钟硬顶)。What Changed
bin/fm-classify-lib.shgainscrew_stale_class(busy / advancing / monitoring / landed / paused / none) andcrew_stale_is_actively_workingas the single owner of what a silent pane is, andbin/fm-crew-state.shappends ano recent step activitymarker to an active run-step detail whose client activity verdict isquiet(gated onRUN_SOURCE=full, exempting thecimonitor) so a stopped step is no longer read as an actively advancing one. The readout has no production caller in this release; the watcher's stale/wedge consumers keep their existing classification.bin/backends/herdr.shnow reports why a projected close or focus mutation refused (active-tab,unknown-focus,unverified-focus,unverified-pane,agent-state-mismatch,plan-refused,close-failed,workspace-removal-unconfirmed,focus-restore-failed), carries the emptying plan's refusal reason out of its command substitution, and adds anagent-freeclose requirement backed by the newfm_backend_herdr_pane_agent_freepredicate (no-agentorstale-agent).bin/fm-herdr-session-cleanup.shgets the read-only--candidate-ready <task-id> [--ignore-focus] [--pending-meta]mode built on the sweep's own proof functions, sharing one candidate-proof sequence with the close path;bin/fm-teardown.shdefers a close only when it was refused specifically foractive-taband that check proves the next sweep would take the candidate, retiring the journal against the pane-presence reading. Every other refusal still holds all records, and the unlanded-work and captain-focus guards are unchanged.docs/architecture.md,docs/herdr-backend.md, anddocs/verification/runtime-backends.md.Risk Assessment
Testing
通过 bin/fm-test-run.sh 运行了 intent 定向的六套件集合(未使用 --all):6 个全部通过,0 失败、0 gate skip,耗时 23m20s。D1 修复针对真实的 teardown 与 session-cleanup 可执行文件做了端到端驱动:一个 landed task,其投影 pane 位于 captain 的 active tab 上且为 agent-free 的 idle shell,现在能完成其 record cleanup,保持 pane 打开、presentation journal 不变,随后真实的 session-start sweep 恰好关闭该 pane 并 retire 该 journal。两个对抗性守卫被驱动并通过:一个 agent-free 但并非干净 lone idle childless shell 的 pane,以及一个仍持有 live agent 的 pane,二者都保留全部 record 且不关闭任何东西,因此不可能产生 orphaned deferred close。round-1 的 plan-refusal-reason 修复通过真实的 herdr backend 驱动:plan 将
active-tab带出其 command substitution,因此只有获得许可的 refusal 才能 defer。--candidate-ready作为真实 subprocess 驱动,仅对确切的 licensed candidate 回答 ready,其余一律以 read-only 方式拒绝。对于 readout 部分,真实的bin/fm-crew-state.sh会为本 crew 自身的 quiet run 追加· no recent step activity,而绝不会把外部 coarse-lane run 的 quiet verdict 断言到本 crew 上;真实的crew_stale_class/crew_stale_is_actively_working函数在库级被针对 stub verdict 执行(readout-six-state-transcript.txt,mismatches=0),但由于 intent 有意将生产消费者排除在本 release 之外,该三态 readout 未被任何运行中的产品调用,因此未获实时结果。Focus protection(captain 的 active tab 从未被关闭、无 workspace close)与 unlanded-work refusal 保持完好。Reviewer 可见的 artifacts:完整套件日志、一段聚焦摘录,以及两份手动 CLI transcript。此变更不存在 UI surface,因此未生成截图。ok - herdr projection teardown defers a focus-blocked close only when the session-start sweep then takes it, instead of orphaning a landed task(真实的 bin/fm-teardown.sh + 真实的 bin/fm-herdr-session-clea…ok - herdr projection teardown keeps every record when the focus-blocked pane is agent-free but not a clean idle shell(focused-evidence-excerpt.txt)ok - herdr projection teardown keeps every record when the focus-blocked pane still holds a live agent(focused-evidence-excerpt.txt)active-tabrefusal 才许可 deferral;plan 级的 focus checkpoint refusal 会将其自身 reason 带出其 command substitution 传给 caller,因此该 reason 不会被丢失或伪造ok - herdr presentation cleanup: a plan refusal carries the focus checkpoint's own reason to the caller驱动真实的 bin/backends/herdr.sh 函数,并断言rc=1 reason=active-tab,且无 close/move(focused-evidence-exc…fm-herdr-session-cleanup.sh --candidate-ready是一个 read-only gate:仅对确切的 licensed candidate 回答 ready,拒绝所有其他 candidate 或缺失 candidate,并在调用格式错误时以 exit 2 退出,不做任何变更ok - candidate-ready answers only for the exact licensed resumable candidate,加上真实 subprocess transcript candidate-ready-cli-contract.txt(usage exit 2、未知 flag exit 2、no-such-candidate exit 1)· no recent step activity,而绝不会把外部 coarse-lane 分支的 quiet verdict 断言到本 crew 上ok - a foreign quiet run cannot mark this crew, its own quiet run still does(真实的 bin/fm-crew-state.sh CLI,配合伪造的 no-mistakes/tmux)crew_stale_class正确读出 busy / advancing / monitoring / landed / paused / none(包括将 pipeline 自身的 no-progress 标记读为 none),且crew_stale_is_actively_working仅在 busy 与 advancing 时为 truecrew_stale_class/crew_stale_is_actively_working(readout-six-state-transcript.txt),并不是对运行中…workspace close;同一套件中既有的 refusal(retains every record when post-close presence is unknown、unlanded-work refusal 用例)仍以 failed=0 通过Evidence: Intent 定向的 no-mistakes Test 套件日志(6 个套件,23m20s,failed=0,skipped_gate=0)
Source: Intent 定向的 no-mistakes Test 套件日志(6 个套件,23m20s,failed=0,skipped_gate=0)
FM_TEST_SUMMARY total=6 failed=0 skipped_gate=0 duration_ms=1400187Evidence: 聚焦证据摘录:D1 deferral + 守卫、plan-refusal reason、candidate-ready、三态 readout、no-progress 标记
Source: 聚焦证据摘录:D1 deferral + 守卫、plan-refusal reason、candidate-ready、三态 readout、no-progress 标记
## D1 teardown deferral ok - herdr projection teardown defers a focus-blocked close only when the session-start sweep then takes it, instead of orphaning a landed task ok - herdr projection teardown keeps every record when the focus-blocked pane is agent-free but not a clean idle shell ok - herdr projection teardown keeps every record when the focus-blocked pane still holds a live agent ## Round-1 plan-refusal-reason fix ok - herdr presentation cleanup: a plan refusal carries the focus checkpoint's own reason to the caller ## three-state readout ok - crew_stale_class: busy, the quiet external waits (advancing/monitoring/paused), landed, and the dead/no-progress cases each read as their own stateEvidence: crew_stale_class 六态 readout transcript(真实的 bin/fm-classify-lib.sh,mismatches=0)
Source: crew_stale_class 六态 readout transcript(真实的 bin/fm-classify-lib.sh,mismatches=0)
ok state: working · source: pane · harness busy (native) busy yes ok state: working · source: run-step · validating (running) advancing yes ok state: working · source: run-step · ci running monitoring no ok state: parked · source: run-step · parked at review gate monitoring no ok state: done · source: run-step · run passed: PR held for merge landed no ok state: paused · source: status-log · awaiting upstream paused no ok state: working · source: run-step · validating (running) · no recent step activity none no mismatches=0Evidence: fm-herdr-session-cleanup.sh --candidate-ready CLI 契约 transcript
Source: fm-herdr-session-cleanup.sh --candidate-ready CLI 契约 transcript
$ bin/fm-herdr-session-cleanup.sh --candidate-ready usage: ... --candidate-ready <task-id> [--ignore-focus] [--pending-meta] exit=2 $ bin/fm-herdr-session-cleanup.sh --candidate-ready --bogus x fm-herdr-session-cleanup.sh: unknown flag --bogus exit=2 $ bin/fm-herdr-session-cleanup.sh --candidate-ready task-x1 exit=1Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/backends/herdr.sh:1115- This assignment claims "the gate's own reason is the accurate one whenever it left one", but the only way this branch can carry a reason is the mutation gate called at line 1322 inside fm_backend_herdr_emptying_close_plan, and that function is always invoked through a command substitution (line 1100 here, line 3402 in fm_backend_herdr_kill_serialized). The gate's FM_BACKEND_HERDR_PROJECTION_MUTATION_REFUSAL assignment happens in that subshell and never reaches this shell, so the variable here is empty (falling back toplan-refused) or, if a caller ever calls the gate earlier in the same process, an unrelated stale value. Concrete sequence: the captain moves focus onto the task pane's own tab during the window between the focus snapshot at line 1056 (which still showed the target workspace != focused workspace, so the plan takes the reposition branch at 1298-1325) and the plan's own gate call; the gate returns 1 with reasonactive-tab, the plan printsrefuse, and this line recordsplan-refused. bin/fm-teardown.sh:3987 only licenses its durable deferral when the reason isactive-tab, so it keeps every record and refuses — the landed pane stays alive with no owner and the next supervision round re-reads it as a wedge, which is exactly the D1 failure this change exists to remove. The same unreliability also means a future in-process caller that had earlier hit anactive-tabrefusal for a different tab could mis-license a deferral. Fix: have the plan carry its own reason out of the subshell (e.g. its last linerefuse <reason>) and have the caller use that instead of a variable set inside the command substitution.bin/fm-classify-lib.sh:2252- The header of crew_stale_class says "bin/fm-crew-state.sh and the no-progress (真死) arm consume it", but bin/fm-crew-state.sh never calls crew_stale_class — it is the producer of the· no recent step activitymarker that this readout consumes, and grep over bin/ shows no production caller of crew_stale_class or crew_stale_is_actively_working at all (the stale/wedge consumers were deliberately left on main). The direction is reversed and the only remaining consumers are tests, so a maintainer reading this contract would look for wiring that is not there. State the actual direction (this readout reads the marker fm-crew-state.sh emits) and that no production consumer is wired in this release yet.🔧 Fix applied.
2 infos still open:
docs/architecture.md:13- The paragraph this change added sayscrew_stale_class"is consumed by the crew-state no-progress marker, which reports a run step with no activity behind it as no-progress rather than as an active step". The direction is reversed: bin/fm-crew-state.sh emits the· no recent step activitymarker and crew_stale_class reads it (bin/fm-classify-lib.sh:2314), which is exactly the reversal this same change corrected in the fm-classify-lib.sh header (line 2252). It also contradicts that corrected header, which states no production consumer is wired in this release. Fix is wording only: state that the readout reads the crew-state no-progress marker, and that nothing in production calls it yet.tests/fm-teardown.test.sh:2787- test_herdr_projection_teardown_refuses_deferral_for_a_non_idle_shell_pane passes FM_FAKE_HERDR_AGENT_NOT_IDLE_SHELL=1, but that flag only makes the fake'sagent getanswer agent_not_found. Thepane process-infobranch that models "agent-free registration over a pane that is NOT a clean idle childless shell" (two foreground processes) is gated on FM_FAKE_HERDR_SHELL_NOT_IDLE, which no test sets, so it is dead code. The test therefore refuses the deferral because pane process-info errors with pane_not_found, not because the lone-idle-childless-shell proof rejected a non-idle shell as its name claims. The refusal assertion still holds, but the named safety boundary is not actually exercised at the teardown level. Fix: set FM_FAKE_HERDR_SHELL_NOT_IDLE=1 there so the intended branch runs.✅ **Test** - passed
✅ No issues found.
ok - herdr projection teardown defers a focus-blocked close only when the session-start sweep then takes it, instead of orphaning a landed task(真实的 bin/fm-teardown.sh + 真实的 bin/fm-herdr-session-clea…ok - herdr projection teardown keeps every record when the focus-blocked pane is agent-free but not a clean idle shell(focused-evidence-excerpt.txt)ok - herdr projection teardown keeps every record when the focus-blocked pane still holds a live agent(focused-evidence-excerpt.txt)active-tabrefusal 才许可 deferral;plan 级的 focus checkpoint refusal 会将其自身 reason 带出其 command substitution 传给 caller,因此该 reason 不会被丢失或伪造ok - herdr presentation cleanup: a plan refusal carries the focus checkpoint's own reason to the caller驱动真实的 bin/backends/herdr.sh 函数,并断言rc=1 reason=active-tab,且无 close/move(focused-evidence-exc…fm-herdr-session-cleanup.sh --candidate-ready是一个 read-only gate:仅对确切的 licensed candidate 回答 ready,拒绝所有其他 candidate 或缺失 candidate,并在调用格式错误时以 exit 2 退出,不做任何变更ok - candidate-ready answers only for the exact licensed resumable candidate,加上真实 subprocess transcript candidate-ready-cli-contract.txt(usage exit 2、未知 flag exit 2、no-such-candidate exit 1)· no recent step activity,而绝不会把外部 coarse-lane 分支的 quiet verdict 断言到本 crew 上ok - a foreign quiet run cannot mark this crew, its own quiet run still does(真实的 bin/fm-crew-state.sh CLI,配合伪造的 no-mistakes/tmux)crew_stale_class正确读出 busy / advancing / monitoring / landed / paused / none(包括将 pipeline 自身的 no-progress 标记读为 none),且crew_stale_is_actively_working仅在 busy 与 advancing 时为 truecrew_stale_class/crew_stale_is_actively_working(readout-six-state-transcript.txt),并不是对运行中…workspace close;同一套件中既有的 refusal(retains every record when post-close presence is unknown、unlanded-work refusal 用例)仍以 failed=0 通过bin/fm-test-run.sh tests/fm-watch-triage.test.sh tests/fm-crew-state.test.sh tests/fm-teardown.test.sh tests/fm-herdr-session-cleanup.test.sh tests/fm-backend-herdr.test.sh tests/fm-daemon.test.sh(intent 定向,未使用 --all): FM_TEST_SUMMARY total=6 failed=0 skipped_gate=0ok - herdr projection teardown defers a focus-blocked close only when the session-start sweep then takes it, instead of orphaning a landed taskok - herdr projection teardown keeps every record when the focus-blocked pane is agent-free but not a clean idle shellok - herdr projection teardown keeps every record when the focus-blocked pane still holds a live agentok - herdr presentation cleanup: a plan refusal carries the focus checkpoint's own reason to the callerok - candidate-ready answers only for the exact licensed resumable candidateok - the sweep treats the registered post-agent-exit reading as agent-free, with the idle shell proof和ok - a registered post-agent-exit reading still needs the lone idle childless shell proofok - a foreign quiet run cannot mark this crew, its own quiet run still doesok - crew_stale_class: busy, the quiet external waits (advancing/monitoring/paused), landed, and the dead/no-progress cases each read as their own state手动驱动:source 真实的bin/fm-classify-lib.sh,并在 11 组 state/source/detail 输入上调用crew_stale_class/crew_stale_is_actively_working-> readout-six-state-transcript.txt (mismatches=0)手动驱动:真实的bin/fm-herdr-session-cleanup.sh --candidate-readysubprocess -> usage exit 2(缺少 id)、exit 2(未知 flag)、exit 1(无此 candidate)-> candidate-ready-cli-contract.txt✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.