Conversation
…perform A ship task whose checks are green keeps its worker, its branch and its merge poll by design: the work is committed and not landed, so teardown is refused and would remove the very poll that will notice the merge. Its idle pane therefore re-wedged on every new pane hash, measured at one alarm every 90 to 150 seconds on 2026-08-20, each costing a supervision turn. With four such pull requests open at once this was the single largest source of pointless wakes. Neither obvious escape works. A `paused:` status line loses to the still-open run, because the absorb classification reads authoritative crew state and the run step outranks the status log. Tearing the task down is forbidden and would disarm the poll. So this adds a declared, durable acknowledgement at the classifier: - bin/fm-merge-wait.sh writes state/<id>.merge-wait, refusing unless the task records a pull request AND its merge poll is armed, and re-verifying that the classifier actually admits what it wrote. - fm-classify-lib.sh's merge_wait_declared is a pure read of that record; the new merge-wait token comes out of crew_absorb_class's SAME single crew-state read, admitted only from the run step's own terminal checks-green outcome. - fm-watch.sh absorbs that stale on the long re-surface cadence, anchored on the declaration's own mtime, and re-reads authoritative state at most once per wedge window so a pull request that stops being green returns to ordinary aging with its declaration still on disk. - Withdrawing the declaration releases the absorbed pane hash with it, so a window never stays quiet on a declaration that no longer holds. - Teardown removes the record with the rest of the task's state. Deliberately narrow. Green alone never silences a pane: on 2026-08-21 three merge watches were found pointing at heads that no longer existed and one merge had already landed unnoticed. The merge poll is never touched, and gating the declaration on it means a declaration can never silence more than that poll already covers. The checks-green detail string becomes a shared constant, because it is the byte-for-byte coupling between the reader that writes it and the classifier that matches it. Tests extend the colocated classifier suite: the declared wait absorbed and re-surfaced, not absorbed when the run is not at that outcome, not absorbed when the recorded pull request is missing or changed, an undeclared green task aging exactly as before, a withdrawn declaration releasing its pane, and the merge poll surviving every one. Each assertion was proved non-vacuous by breaking the corresponding code and watching it fail.
…asserting exclusivity
…ait suppressor helper
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains within the eligible follow-up scope. No blocking failure remains. Reviews (2): Last reviewed commit: "no-mistakes(lint): narrowly suppress fal..." | Re-trigger Greptile |
Inthuson
force-pushed
the
fm/ship-done-merge-wait-declaration-d7
branch
from
August 22, 2026 18:54
4c1f480 to
874580b
Compare
This was referenced Aug 24, 2026
dnth
added a commit
to dnth/firstmate
that referenced
this pull request
Sep 11, 2026
#133) * fix(bin): accept converged synchronized runs in validation binding A correctly attributed no-mistakes run that passed its required checks stays active only to monitor its open PR. In that state `axi status --run` still reports status running with no outcome and no branch_sync block, while `axi sync --check` reports the branch converged: state synchronized, safety already_synchronized, relation equal, and the pipeline block naming the same run, its submitted head, and its pushed-back current head. Both completion paths required pipeline_owned, so the bound run could never bind or seal its own advance (observed on fm-omp-orchestrate-opt-in run 01M25YRZ7NP7HVZ33K1XC94AYN, PR #131: submitted 5d52705, pushed c096921, checks green, still monitoring). Add fm_nm_run_branch_ownership to bin/fm-nm-run-lib.sh as the one owner of active-run branch evidence, used by both --bind-run and --complete. It keeps pipeline_owned acceptance unchanged and additionally accepts a synchronized state only on the full sync readout: same run id, submitted_head resolving to the validated head, current_head and the reported local head resolving to the run's observed head, relation equal, and safety already_synchronized. Checks-green readiness stays a separate requirement, so a merely synchronized-but-not-passed run still refuses. Upstream kunchenguid/firstmate has no fix to port: fm-receipt-check.sh is fork-local and upstream's diverged fm-nm-run-lib.sh has no synchronized handling. Adjacent upstream repairs (kunchenguid#2881, kunchenguid#3681, kunchenguid#3704, kunchenguid#3846) cover other read paths; open upstream PR kunchenguid#2799 confirms the checks-green merge-wait state but targets the supervision classifier, not completion. Extend tests/fm-receipt-check.test.sh with the converged bind+complete path and negative controls for foreign run ids, mismatched submitted heads, incomplete convergence fields, stale local heads, and synchronized-with-pending-CI completion. * no-mistakes(document): Correct run-owned branch wording in verification evidence
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
Give a checks-green ship task a way to declare, durably, that it is deliberately waiting on a merge nobody in this fleet can perform, so possible-wedge aging stops without closing the task, exiting the worker, or removing the merge poll.
Why: this is live, not hypothetical. Measured on a real task on 2026-08-20, the watcher alarmed every 90 to 150 seconds on an idle green pane, each alarm costing firstmate a handling turn, with no way to suppress it. As of 2026-08-22 there are four firstmate PRs in exactly this state at once, all green or unrunnable and all waiting on a maintainer this fleet has no control over, plus a fifth on GitLab. This is the steady state of the whole firstmate lane and the single largest source of pointless supervision turns.
Both obvious escapes were verified wrong before choosing this design. (1) A 'paused:' status line does not help: crew_absorb_class reuses bin/fm-crew-state.sh, whose authoritative run-step read wins, so a declared wait loses to the still-open run. The captain explicitly ruled out 'fixing' this by re-declaring a pause harder or adding a new pause verb. (2) Teardown is forbidden because the work is committed and not landed; data/learnings.md records that exiting or tearing down over an open run once cost a twelve-hour-late push and a false delivery record, and teardown removes the merge poll that would have surfaced the merge. So the missing capability is a declared, durable acknowledgement at the classifier, not a new pause verb and not a lifecycle shortcut.
Requirements the captain stated, all of which must remain true and are deliberate rather than oversights:
Scope was deliberately constrained by the captain: 'This is a classifier change. Do not restructure bin/fm-crew-state.sh's state model, do not unify the several places outcome truth is derived, and do not generalise this into a framework for arbitrary declared waits.' An adjacent task (brief-required-external-wait-w7) covers the required-external-wait brief work and was explicitly not to be absorbed. Structural problems found along the way were to be recorded as a note for firstmate to file, not fixed: one such note was filed about bin/fm-supervise-daemon.sh classifying stale panes through its own classify_stale, which never calls crew_absorb_class, so a declared merge wait is still escalated while away mode owns supervision. That second-consumer change is deliberately out of scope here.
Design decisions I made while implementing:
Testing approach the captain asked for: extend the existing colocated classifier tests in tests/fm-watch-triage.test.sh rather than inventing a new harness (a new test file would also require shard/family/timing registration in bin/fm-test-run.sh). Six tests cover a declared wait on a genuinely green recorded PR being absorbed and re-surfaced, the same declaration not absorbing when the run is not at a terminal outcome, the declaration not absorbing when the recorded PR is missing or changed, a task with no declaration aging exactly as it does today, a withdrawn declaration releasing its absorbed pane, and the merge poll surviving in every case. The captain required the new assertions be proved non-vacuous: each was, by five targeted breakages of the implementation, with the matching test failing every time and passing again on restore.
What Changed
bin/fm-merge-wait.sh(declare/show/clear), which writes a durablestate/<id>.merge-waitrecord naming the pull request being waited on.declarerefuses unless the task records apr=and that pull request's own registered merge poll is still armed (distinguishing a custom check in the slot, with its own message),bin/fm-teardown.shnow removes the record with the rest of a task's state, andAGENTS.md,docs/architecture.md,docs/configuration.md, anddocs/scripts.mddocument the record, the declaration workflow, and the reminder cadence.bin/fm-classify-lib.shgainsmerge_wait_declaredandmerge_wait_poll_armedand a newmerge-waittoken fromcrew_absorb_class(now taking an optional state dir), admitted only when the authoritative run step reports the terminal checks-green detail — extracted as the sharedFM_CLASSIFY_CHECKS_GREEN_DETAILconstant thatbin/fm-crew-state.shnow emits, so writer and reader cannot drift.bin/fm-watch.shroutes every suppressible stale wake through a newemit_stale_wakethat requires an explicit alarm class, andmerge_wait_absorbs_alarmabsorbs onlyidle-stalealarms — never thebusy-turnbound, never in away mode, never over a crew's ownpaused:/captain-heldline — re-reading the declaration and a fresh crew-state verdict per alarm, passing one reminder through perFM_PAUSE_RESURFACE_SECSanchored on the record's mtime, leaving the merge poll armed and deferring.wedge-escalations-<key>writes to surfaced alarms only;clear_merge_wait_markersretires the throttle when a declaration stops holding. Covered by 26 new tests (24 intests/fm-watch-triage.test.sh, one each in the crew-state and teardown suites).Risk Assessment
✅ Low: The final round's two behavioral changes are narrow and verified correct against source (the escalation count is now written only on the surfaced path before the exiting wake, and the poll gate checks the same registration evidence the poll's own validator uses, shared with declare so the two cannot disagree), every intent criterion is satisfied, the single-emit-point, alarm-class, and away-mode invariants all still hold, and the only surviving findings are comment-completeness and test-fixture fidelity with no behavioral consequence.
Testing
Baseline for this round was the two commits the previous rounds landed on the test file, so I re-ran the change's own targeted coverage rather than the suite: the 24 declared-merge-wait cases in tests/fm-watch-triage.test.sh, the teardown retirement case, and the 6 timing-sensitive cases those rounds repaired, the last of which I ran 7 times in a row on a host at load average 70 to confirm the write-chain race and the reap hang are actually gone (42 assertions, no failures, no wedge). I independently re-proved the two load-bearing fixes non-vacuous by deleting the implementation line each covers, watching the matching test fail, and restoring. I then found and closed one real coverage gap in the change's own coupling:
outcome: checks-passedhad no fixture anywhere in the suite, so I added tests/fm-crew-state.test.sh:test_checks_passed_outcome_admits_a_declared_merge_wait, which drives a real checks-passed run through the real reader, the real merge poll, the real declaration writer and the real classifier and asserts merge-wait with the declaration and none without it; a drifted literal on that branch reds it while the existing triage cases stay green, which is precisely the blindness it removes. For reviewer-visible product evidence I built and ran an end-to-end demo through the real CLIs and captured its transcript, which quantifies the intent: 3 alarms across 3 pane hashes undeclared versus 0 declared, the refusals when no merge poll is armed and when a custom check occupies that slot, one bounded reminder per cadence with its own wording, the alarm returning when the run goes red or the recorded pull request changes, withdrawal releasing the pane and retiring the throttle, and the still-armed merge poll reportingmergedwhile the declaration is live. There is no UI surface in this change, so the reviewer-visible artifact is a CLI transcript plus the watcher's own triage log and durable wake queue rather than a screenshot. Everything passed; the only working-tree change I leave behind is the added test, and all scratch runners were removed.Evidence: End-to-end CLI transcript: declare, absorb, bounded reminder, refusals, withdrawal, and the merge landing
=== STEP 0 the task, as firstmate left it: green PR, worker alive, merge poll armed $ bin/fm-pr-check.sh shipdemo https://github.com/example/repo/pull/4242 (arm the merge poll that will report the merge) armed: state/shipdemo.check.sh $ bin/fm-crew-state.sh shipdemo (the AUTHORITATIVE run-step read the classifier trusts) state: done · source: run-step · checks green: PR ready for review === STEP 1 TODAY, with no declaration: the idle green pane alarms (the measured noise) watcher woke firstmate and exited. reason it delivered: > stale: fm:fm-shipdemo $ bin/fm-wake-drain.sh 1787415857 1 stale fm:fm-shipdemo stale: fm:fm-shipdemo merge poll still armed: state/shipdemo.check.sh, state/shipdemo.pr-poll, state/shipdemo.pr-poll-registration === STEP 2 the declaration REFUSES to be made without an armed merge poll $ bin/fm-merge-wait.sh declare nopoll error: nopoll has no armed merge poll (run bin/fm-pr-check.sh first) exit status: 1 (record written? no) ... and refuses when the check slot holds some OTHER check, not this merge poll: $ bin/fm-merge-wait.sh declare custom error: custom has a check armed, but it is not this task's merge poll for https://github.com/example/repo/pull/4242 (re-arm with bin/fm-pr-check.sh) record written? no === STEP 3 firstmate declares the wait on the real task $ bin/fm-merge-wait.sh declare shipdemo "upstream maintainer owns this merge" declared: state/shipdemo.merge-wait (https://github.com/example/repo/pull/4242) $ bin/fm-merge-wait.sh show shipdemo pr=https://github.com/example/repo/pull/4242 declared=1787415860 note=upstream maintainer owns this merge === STEP 4 the same pane now goes quiet: no wake, nothing queued, merge poll untouched watcher still running after 4 polls (it did NOT wake firstmate) watcher stdout (what firstmate would have been told): <empty> durable wake queue: <empty> state/.watch-triage.log: [2026-08-22T16:24:28+0000] absorbed stale (declared merge wait, age 8s): fm:fm-shipdemo the pane text now CHANGES (a clock tick) - the case measured on 2026-08-20, where each new pane hash cost one alarm. It stays absorbed: still running after the new hash. queue: <empty> [2026-08-22T16:24:39+0000] absorbed stale (declared merge wait, age 19s): fm:fm-shipdemo merge poll still armed: state/shipdemo.check.sh, state/shipdemo.pr-poll, state/shipdemo.pr-poll-registration === STEP 5 suppression is not silence: one reminder per FM_PAUSE_RESURFACE_SECS the reminder firstmate receives: > stale: fm:fm-shipdemo (merge wait 504s, checks green and awaiting a merge this fleet cannot perform, rechecked on a long cadence not a wedge; confirm the merge is still someone else's to make) and inside the same window it goes quiet again: no second reminder. watcher stdout: <empty> === STEP 6 the declaration does NOT silence a pull request that is not green $ bin/fm-crew-state.sh shipdemo state: failed · source: run-step · run failed the declaration was ignored and firstmate was woken: > stale: fm:fm-shipdemo === STEP 6b green again, but the watch was re-armed on a DIFFERENT pull request declaration on disk still says: pr=https://github.com/example/repo/pull/4242 task metadata now records: pr=https://github.com/example/repo/pull/4300 and the authoritative verdict is green again, so ONLY the mismatch is at play: state: done · source: run-step · checks green: PR ready for review the superseded declaration was ignored and firstmate was woken: > stale: fm:fm-shipdemo === STEP 7 withdrawing the declaration returns the pane to ordinary aging $ bin/fm-merge-wait.sh clear shipdemo cleared: state/shipdemo.merge-wait firstmate is woken again, exactly as before the declaration existed: > stale: fm:fm-shipdemo merge poll still armed: state/shipdemo.check.sh, state/shipdemo.pr-poll, state/shipdemo.pr-poll-registration reminder throttle retired? yes === STEP 8 the measured cost, counted: one alarm per new pane hash, before and after (a) with NO declaration: hash 1 -> WOKE firstmate: stale: fm:fm-shipdemo hash 2 -> WOKE firstmate: stale: fm:fm-shipdemo hash 3 -> WOKE firstmate: stale: fm:fm-shipdemo undeclared: 3 alarm(s) across 3 distinct pane hashes (b) with the declaration in place: hash 1 -> no wake hash 2 -> no wake hash 3 -> no wake declared : 0 alarm(s) across 3 distinct pane hashes === STEP 9 the merge finally lands: the poll the declaration never silenced reports it declaration still live? yes firstmate is woken by the merge poll, not by a wedge alarm: > check: .../state/shipdemo.check.sh: merged === ENDEvidence: The demo script that produced the transcript (real firstmate CLIs, only tmux / no-mistakes / gh stubbed)
Evidence: Round 3 evidence summary, mapping each intent requirement to the step that shows it
/tmp/no-mistakes-evidence/01M0M20RC149A8DXDPT5JZ6RNV/e2e-workdir/state)Evidence: The 24 declared-merge-wait cases
Evidence: Timing-sensitive cases, 7 consecutive runs at load average 70
run 1 exit=0 run 2 exit=0 run 3 exit=0 run 4 exit=0 run 5 exit=0 run 6 exit=0 === aggregate === 6 ok - provably-working non-terminal stale is absorbed on first sight, then wedge-escalated past the threshold 6 ok - matching non-terminal stale suppressors repair missing or corrupt stale-since timers 6 ok - both first-sight paths through a captain-relevant status drop a finished write-deferral chain with the idle window 6 ok - an idle-window timer repair drops a finished write-deferral chain, so the next deferral gets a fresh re-surface window 6 ok - a write deferral re-surfaces once on the bounded pause cadence, so a churning worktree cannot stay invisible 6 ok - a quiet pane writing its own worktree is deferred, while one writing nothing still wedge-escalates on the unchanged scheduleEvidence: tests/fm-crew-state.test.sh after the added case
Source: tests/fm-crew-state.test.sh after the added case (local file:
/tmp/no-mistakes-evidence/01M0M20RC149A8DXDPT5JZ6RNV/crew-state-file.log)Pipeline
Updates from git push no-mistakes
... (14 earlier update rounds omitted to keep the PR body within GitHub's 65536-char limit; full history is in the run log.)
🔧 **Test** - 3 issues found → auto-fixed (2) ✅
🔧 Fix: test: synchronize write-chain assertion on the file it asserts
✅ Re-checked - no issues remain.
bash tests/ztmp-mergewait.test.sh(a subset runner over tests/fm-watch-triage.test.sh definitions with only the invocation list narrowed to the 24 declared-merge-wait and absorbed-alarm cases): 24 ok, 0 not ok, 5m20sThe 6 timing-sensitive cases the previous rounds changed (test_nonterminal_stale_provably_working_absorbed_then_escalated,test_nonterminal_stale_repairs_missing_or_corrupt_timer,test_wedge_escalation_deferred_while_worktree_is_written,test_write_deferral_resurfaces_on_the_bounded_cadence,test_timer_repair_drops_a_finished_write_deferral_chain,test_terminal_first_sight_drops_a_finished_write_deferral_chain) run 7 times consecutively at load average ~70: 42 ok, 0 not ok, no reap hangtests/fm-teardown.test.sh->test_teardown_retires_a_declared_merge_wait: 1 okbash tests/fm-crew-state.test.sh(whole file, after adding the new case): 54 ok, 0 not ok, 34sNon-vacuity: deletedmerge_wait_declared "$task" "$STATE" || clear_merge_wait_markers "$key"from bin/fm-watch.sh ->not ok - the withdrawn declaration kept its reminder throttle(test_merge_wait_withdrawal_retires_the_reminder_throttle), then restoredNon-vacuity: deletedclear_write_tracking "$(window_key "$win")"from wedge_timer_check's repair branch ->not ok - an idle-window timer repair kept a finished write-deferral chain, then restored, confirming the newwait_absentwait did not make that assertion unfailableNon-vacuity of the added test: hardcoded a drifted literal on bin/fm-crew-state.sh's checks-passed branch -> new testnot okwhiletest_terminal_stale_merge_wait_absorbs_each_new_hashstayedok, then restoredEnd-to-end manual verification:EV=... ROOT=$PWD bash /tmp/no-mistakes-evidence/01M0M20RC149A8DXDPT5JZ6RNV/merge-wait-e2e-demo.shdriving the real bin/fm-pr-check.sh, bin/fm-crew-state.sh, bin/fm-merge-wait.sh (declare/show/clear), bin/fm-watch.sh and bin/fm-wake-drain.sh over a real git worktree and a realoutcome: checks-passedrun, with only tmux, the no-mistakes CLI and gh stubbed🔧 **Document** - 2 issues found → auto-fixed (3) ✅
docs/architecture.md:35- docs/architecture.md lines 33-37 carry a second full copy of the accepted-limit contract (reminder scope, the merge poll as the single cover, why the heartbeat review is not a second cover, the poll-died exposure, and the terminal-outcome immutability rationale) that merge_wait_absorbs_alarm's header in bin/fm-watch.sh explicitly claims to own: 'THE ACCEPTED LIMIT, stated once and in full, because every other place that touches it points here rather than restating it'. Both copies are currently accurate and one is test-pinned (tests/fm-watch-triage.test.sh:2140 asserts the architecture.md reminder claim), so I left the prose intact rather than deleting accepted maintainer-architecture safety rationale. Judgment call for the author: reduce those lines to a short statement plus a pointer to the implementation header, or move the uniqueness claim out of the code comment. Out of scope to decide here.tests/fm-watch-triage.test.sh:2015- Observed while verifying my own comment edit, not caused by it and not a documentation gap: bin/fm-lint.sh exits 1 on this branch because of SC2016 (info) at tests/fm-watch-triage.test.sh:2015, in the single-quotedbash -cbody of merge_wait_suppressor_verdict. The file is unmodified in the working tree, so the finding is in committed test code. shellcheck is clean on both files I touched. Reported only so it is not lost; the lint phase owns it and tests are outside this phase's edit remit.🔧 Fix: point architecture.md at the accepted-limit owner
3 infos still open:
tests/fm-watch-triage.test.sh:2140- Part 2's premise did not hold, reported rather than assumed. tests/fm-watch-triage.test.sh:2140 is a comment header, not an assertion: it is the rationale block above test_merge_wait_reminder_passes_through_from_the_wedge_window, and no test anywhere in tests/ reads the bytes of docs/architecture.md or of any bin/fm-watch.sh comment (verified by grepping the whole suite for reads of those paths). So there was no pin to re-aim, and part 3's 'delete the claim, watch the assertion fail' could not be performed. I ran the inverse demonstration instead: with the entire accepted-limit paragraph including the exclusivity claim deleted from the header, the full file passed 84/84 exit 0, identical to the baseline run, and the wedge-window reminder test passed in both. On which claim it cited: the wording 'the bounded reminder wherever an alarm attempt recurs' appears at BOTH docs/architecture.md:12 and the now-removed line 33, so it was ambiguous between them; I re-aimed the citation at merge_wait_absorbs_alarm's header, which is now the single owner of that reminder's reach. Remaining gap for you to decide: if you want the header claim genuinely pinned, that means NEW test code (a case asserting bin/fm-watch.sh still contains the claim sentence), which is executable test authoring and outside this phase's edit remit. Proposed as follow-up for the test phase rather than done here.docs/configuration.md:681- The uniqueness audit's one unresolved touchpoint, left alone under your explicit instruction. docs/configuration.md:681 still enumerates the reminder's reach in the FM_PAUSE_RESURFACE_SECS entry ('wherever an alarm attempt recurs: a pane whose hash keeps changing, or the non-terminal wedge window, and not a byte-static pane on the terminal branch, which attempts no alarm at all once its hash is recorded'), which restates the first face of the accepted limit without pointing at the header. You directed me not to disturb line 681, and trimming the enumeration to a pointer would edit it, so I did not. My judgment on why the header's claim survives this: line 681 states only the setting's own scope, not the limit, since it says nothing about the merge poll as sole cover, the heartbeat non-cover, the residual exposure, or the terminal-outcome immutability, and the guidelines allow one deliberate reinforcement at a genuine risk point (an operator reading this setting would otherwise take the reminder as unconditional). If you want the header's sole-ownership claim absolute, the edit is to keep 'wherever an alarm attempt recurs' and replace the trailing enumeration with a pointer to merge_wait_absorbs_alarm, which preserves the qualification the previous round established. That is your call, not mine, because it touches the line you protected.bin/fm-classify-lib.sh:1236- Audited and judged compliant, recorded so the uniqueness check is auditable rather than implied. bin/fm-classify-lib.sh:1234-1236, in merge_wait_poll_armed's header, says 'On a byte-static terminal pane that is silence with no cover at all, since the armed poll is the only cover the suppressor claims (see merge_wait_absorbs_alarm in bin/fm-watch.sh).' That touches two faces of the limit but in a single clause, as the reason this predicate must not be weakened back to a bare -f check.sh test, and it names the owner in the same sentence. That is the one-line reinforcement at a genuine risk point the knowledge-placement policy permits, not a restatement, so I left it. No other place in bin/ or docs/ touches the accepted limit: bin/fm-watch.sh's later reconciliation comment already points at the header and says so explicitly, bin/fm-merge-wait.sh's header states only its own refusal rationale, and AGENTS.md states only the admission gates.🔧 Fix: designate the accepted-limit owner instead of asserting exclusivity
2 infos still open:
docs/architecture.md:33- Part 3 answered, no edit made: I read docs/architecture.md:33-34 and bin/fm-watch.sh:1364-1367 the same way you do, so both stand as they are. architecture.md:33 says the limit 'has exactly one owner: the merge_wait_absorbs_alarm header in bin/fm-watch.sh states in full ...', which is a designation of ownership plus a claim about the pointed-at text; line 34's 'Read it there rather than here' is an instruction about this document only. fm-watch.sh:1364-1367 says the header 'states in full: ... Read it there rather than restating any of it here', which likewise constrains only its own site. Neither asserts anything about files a future editor could change without notice, so neither can silently become false the way the two deleted clauses could. Recorded so the reading is auditable rather than implied.bin/fm-watch.sh:426- One judgment call the new imperative creates, for you to settle rather than me: it reads 'DO NOT restate any of it elsewhere, in code or in docs', with no carve-out, while bin/fm-classify-lib.sh:1234-1236 carries the single clause you authorized as it stands ('since the armed poll is the only cover the suppressor claims (see merge_wait_absorbs_alarm in bin/fm-watch.sh)'). My reading is that the clause obeys the rule, because it names the owner in the same sentence and gives one reason this predicate must not weaken back to a bare -f check.sh test rather than stating the contract, so it is a pointer with local context and not a restatement. But a future reader applying the imperative literally could read it as licence to delete that clause, which would cost the reason the gate exists. I did not add a carve-out because your instruction was to write the designation and the imperative 'and nothing more', and widening the rule myself is exactly the drift you are guarding against. If you want the tension closed, the edit is one clause in the imperative permitting a single-sentence reinforcement that names this owner.🔧 Fix: rescope accepted-limit imperative from restating to adding
✅ Re-checked - no issues remain.
🔧 **Lint** - 1 issue found → auto-fixed ✅
🔧 Fix: narrowly suppress false-positive SC2016 in merge-wait suppressor helper
✅ Re-checked - no issues remain.
✅ **Push** - passed
✅ No issues found.