fix(coord): the dispatch gate's two seat maps named retired seats (BACKLOG #1462) - #937
fix(coord): the dispatch gate's two seat maps named retired seats (BACKLOG #1462)#937wshallwshall wants to merge 5 commits into
Conversation
`CLOSING_SEAT` is not documentation -- `dispatch_gate.judge()` folds its value
straight into the line a dispatcher reads ("closes by X, performed by <value>").
Three of its four values named a seat CLAUDE.md section 5 retired: the ASVS
Tracker, the Liaison and the Dispatcher. Roughly 40 open rows carry
`closing-act: scorecard-rescore` alone.
`scorecard-rescore` now follows the owner ruling of 2026-09-05: the LANDER
performs vault scorecard re-scores. It already held the banner and standing
vault authority, so the act's two steps sit with one seat.
The two-step reasoning is KEPT, because the failure it describes is unchanged:
the re-score lands in a vault file gitignored from every engine checkout, so
nothing in this repository reports that step one happened. Only the shape moved
-- a handoff message between two seats became a sequencing obligation inside one.
The comment cited `BUILDER.md:253` and `:148`. Both had drifted, to :142 and
:216, and neither drift was detectable here: BUILDER.md lives in the vault
`roles/` folder, and `citation_line_check.py` refuses a bare filename rather
than guess. Both are now quotes of the claim.
Two paired arms, because either alone is satisfiable by the other's failure: no
value may name a retired seat, and every value must name a live one. The first
asserts its matcher fires on a known-bad string, and that control earned its
keep -- the word-boundary anchors were written as literal backspace bytes and
the pattern matched nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GATED_VERDICTS in scripts/coord/dispatch_gate.py is a SECOND, independent source of retired seat names -- the LIAISON in both of its values, the Dispatcher in one -- and judge() folds it into the same dispatch output as CLOSING_SEAT. Repairing one alone is worse than repairing neither: a reader who sees the closing act named correctly has no reason to doubt the verdict sentence printed beside it. Reach, measured here by calling judge() over each of origin/main's 434 rows with its own body: ASVS Tracker 97, LIAISON 35, Dispatcher 33 -- 132 distinct rows, 130 open. The routing changed, not just the name. Section 5 retired the Liaison AND the rule that owner questions route through one, so "via the LIAISON" could not be repaired by substitution -- the hop it described is gone. The two assertions pinning LIAISON move in this same commit, because test_coord_dispatch_gate.py is in tests/tooling_manifest.txt and `tooling` is in ci-gate's needs. Both regression arms now walk both maps. Three mutations confirm each arm is real: regressing either constant fires the retired-seat arm and not the names-nobody arm; stripping a value to name nobody fires the second and not the first. Ledger row rescoped under BACKLOG #1460, the peer session's primary filing (PR 936, not yet on main), which carries the joint measurement set. Two corrections to this branch's first version: BUILDER.md:253 is BLANK, not a section heading, and the severity is bounded -- replacing every value in both maps changes 0 gate levels, and no workflow or hook invokes dispatch_gate.py. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It was a scratch file for `git commit -F`, written inside the worktree per CLAUDE.md's rule against the shared scratchpad, then caught by `add -A` before it was deleted. No content change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Peer decision, checked rather than taken on trust. 1460 was allocated first as the primary row for the same fix, with the wider scope. Two rows describing one fix is the ledger decay this fleet spent today correcting, so a second row is not worth adding while fixing the first. The number is kept because git log, PR 937's title and claim.ps1 all cite it. Deleting it turns those into citations that resolve to nothing, and the day someone re-allocates 1462 they start resolving to unrelated work. Retirement is prose, not a banner change, so the filing banner and the fields stay exactly as filed. Verified the dispatch gate SEES it: both needles fire (_RETIRED_HEADING on the heading, _RETIRED_DECLARATION on the body, which is what load_ledger passes after dropping the banner block), and judge() returns `refuse` with "RETIRED IN PLACE -- DO NOT BUILD IT". The peer predicted `advise`; the measured level is stronger. THE RISK IS NAMED IN THE ROW, NOT LEFT TO RIDE. 1460 is not on main -- it is on origin/builder-seat-name-filing behind PR 936, open. If 936 is rejected, 1460 never exists and this retirement stranded the only surviving row for work that shipped. PR 936 should land before PR 937. Also corrects a wrong justification this branch published. It said CLOSING_SEAT["code"] was a routing disagreement between two LIVE sources. It is not: backlog-hygiene.yml and CLAUDE.md section 5's Builder row AGREE that the BACKLOG.md edit rides the same PR as the code, and that PR is the Builder's. The disagreeing source is the vault roles/BUILDER.md:216, which is stale rather than live. Verified both. Disposition unchanged -- still left to 1460, whose heading already covers it -- but the stated reason was wrong. Code and test citations repointed from the retired 1462 to 1460. One mechanical note for whoever hits it next: the pre-commit claim gate reads the `BACKLOG #N` token out of the SUBJECT. The first version of this subject wrote "BACKLOG #1460" and bare "#1462", so the gate read the peer's number and refused the commit for a claim this worktree does not hold. Spell your own number with the token and everyone else's without it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
This pull request is green and nobody has marked it read. Every check that has reported is passing and the To clear it, IN THIS ORDER:
DO NOT TRIAGE THIS FROM A seat can find every pull request in this state in one call: The |
What was wrong -- two constants, not one
judge()indispatch_gate.pyfolds both seat maps straight into the line a dispatcher reads, so neither is documentation:CLOSING_SEAT(backlog_status_check.py)GATED_VERDICTS(dispatch_gate.py)Reach, measured in this checkout by calling
judge()over each oforigin/main's 434 ledger rows with its own body: ASVS Tracker on 97, LIAISON on 35, Dispatcher on 33 -- 132 distinct rows, 130 of them open.How bad it is NOT, so the score is honest
No gate decides wrongly. Replacing every value in both maps with a placeholder changes the level on 0 rows. Positive control on the same experiment: emptying
BUILDER_CLOSABLE_ACTS, the frozenset that actually drives the branch, moves 161. Andgrep -rn dispatch_gate .github/ .pre-commit-config.yamlreturns nothing -- no workflow and no hook invokes it.This is stale prose with a tool as its delivery channel, not a misfiring gate. It still matters, because the prose is the product: the gate exists to NAME a performer rather than refuse an item.
PR 929 has not merged, and nothing here waits on it
gh pr view 929returnsOPEN,mergedAt: null(updatedAt2026-09-06T00:07:47Z). The claim this rests on is the owner ruling of 2026-09-05, which 929 records but does not create; section 5's Lander row onmainalready grants standing vault authority, so the citation resolves either way.claude_section_check.pyexits 0.The routing changed, not just the seat's name
GATED_VERDICTSsaid the ruling "comes via the LIAISON". Section 5 retired the Liaison and the rule that owner questions route through one -- the Console is now the only seat the owner talks to. That clause could not be repaired by substituting a name; the hop it described is gone.scorecard-rescorenames the Lander, which already held the banner and standing vault authority.The two-step reasoning is kept, per the brief, because the failure it describes is unchanged. The re-score lands in a vault file gitignored from every engine checkout, so nothing in this repository reports that step one happened. What moved is the failure's shape: a handoff message between two seats became a sequencing obligation inside one.
The stale citations could not have been caught here
:253:142. Line 253 itself is BLANK, andfiler-- the word the citation's prose used -- appears zero times in the file, withbannerat 7 as the positive control.:148:216. Line 148 is now about ADR numbering.Two independent reasons nothing here could fire:
BUILDER.mdlives in the vaultroles/folder, so no checkout of this repository carries it; andcitation_line_check.pyrefuses bare filenames rather than guess -- 935 of 3,086 citations sit in that bucket. Both are now quotes of the claim.The same-PR constraint is satisfied
tests/test_coord_dispatch_gate.pyis attests/tooling_manifest.txt:84;toolingis in theneeds:list ofci-gate;CI gateis required per.github/required-contexts.txt. All three verified in this checkout. Every constant and its assertions move together here, soCI gatecannot red on a half-applied rename.Arms, and the mutations that earned them
Both arms walk both maps.
CLOSING_SEATregressed to the ASVS TrackerGATED_VERDICTSregressed to the LiaisonThe negative control caught a defect in the arm itself. It asserts the matcher hits a known-bad string before checking the maps -- and that failed on the first run: the word-boundary anchors reached the file as literal backspace bytes (
0x08) through the heredoc unescaping of BACKLOG #1397, so the pattern matched nothing. A silently-broken checker over a repaired map is green.Scope, and three corrections to this body's earlier versions
Widened twice, both flagged rather than quiet. The brief named
CLOSING_SEAT["scorecard-rescore"]. I also fixed its two sibling entries, and thenGATED_VERDICTSafter a peer session measured it as a second independent source and stood its own Builder down on the constants. Shipping one repaired map beside a stale one is worse than shipping neither. Either widening is easy to revert.Correction 1. This body first said PR 929 was "checked again before the push". It was not -- the re-check came after. Verdict unchanged; the provenance sentence was wrong.
Correction 2. It first said
BUILDER.md:253"is now a section heading". It is blank; the heading is at:252.Correction 3. It called
CLOSING_SEAT["code"]"a routing disagreement between two live playbook sources". That was wrong..github/workflows/backlog-hygiene.ymland CLAUDE.md section 5's Builder row agree -- both put thedocs/BACKLOG.mdedit in the same PR that carries the code, and that PR is the Builder's. The source that disagrees is the vaultroles/BUILDER.md:216, which is stale, not live, because section 5 replaced the method it assumes. Verified both. The disposition is unchanged -- still left to #1460 -- but the reason I gave for it was not.Ledger -- #1462 is RETIRED IN PLACE, folded into #1460
The peer session that filed #1460 as the primary row for this same fix asked for the fold, and I took it: two rows describing one fix is the ledger decay this fleet spent today correcting.
The number survives because it must.
git log, this PR's title andclaim.ps1all cite #1462. Deleting it turns those into citations that resolve to nothing -- and the day someone re-allocates 1462, they start resolving to unrelated work.Retirement is prose, not a banner change, so the filing banner and fields stay exactly as filed. I verified the dispatch gate actually SEES it rather than assuming: both needles fire (
_RETIRED_HEADINGon the heading,_RETIRED_DECLARATIONon the body -- which is whatload_ledgerpasses after dropping the banner block), andjudge()returnsrefuse, leading with "RETIRED IN PLACE -- DO NOT BUILD IT". The peer predictedadvise; the measured level is stronger.The risk this takes, named rather than left to ride
#1460 is not on
main. It is onorigin/builder-seat-name-filingbehind #936, open and unmerged; I verified both its heading and #1461's exist there.If PR 936 is rejected outright, #1460 never exists and this retirement stranded the only surviving row for work that shipped. That is the accepted trade, not an oversight. PR 936 should land before PR 937 so the citation resolves. The peer records the same sequencing in the Lander's order file.
Landing: this PR is MERGEABLE today, and the conflict is PROSPECTIVE
A peer session measured PR 937 as conflicting with #936 in
docs/BACKLOG.md. Re-measured against this PR's current headc83e5650f, and it holds -- but the shape matters and the shorthand "937 conflicts" would mislead a Lander:937xorigin/mainmergeable=MERGEABLE937x936's branchdocs/BACKLOG.mdSo nothing is DIRTY right now. The conflict appears only once 936 lands, because both branches append to the tail of
docs/BACKLOG.md-- 936 adds #1460 and #1461, this one adds #1462. Verified there is no duplicate number: 936's branch carries no## 1462heading.The resolution lives in this branch, but it is NOT mine to trigger. Once 936 merges, a rebase resolves it against a
#1460row that exists by then -- a far easier merge than resolving it blind now. This branch will not be rebased until the Lander asks for it. Nothing on 936's side needs changing, and nothing here is waiting on the author.Instrument, with both controls, because a dead
merge-treereturns the same silence as a clean tree: the documented conflicting pair (worktree-agent-acdb9222ba84254fbx...a01b6380fa19e85ea) exits 1, and a branch merged against itself exits 0.Held, as instructed: rows 1122, 1169, 1182, 1188 (PR 935).
docs/BACKLOG.mdshows 0 deletions againstorigin/mainacross all four commits -- append-only, so those rows are provably untouched.Left for #1460, not missed:
CLOSING_SEAT["code"]. See Correction 3 -- my first reason for leaving it was wrong, the disposition was not. It needs the vault half (#1461) and an owner-ruled vault write, so it is not a rename I can land here.Checks
ruff check ./ruff format --check .mypy messagefoundry(strict)pytestledger + coordination suitesbacklog_status_check.pyclaude_section_check.pylink_check.pybacklog_citation_check.pypre-commit(viagit commit)One advisory, not mine.
#1426is open and unscored -- confirmed identical onorigin/mainwithparse_items, which parses 434 items there against 435 here, the one addition being #1462.One pre-existing mypy finding, out of CI scope.
dispatch_gate.py:115reports an unusedtype: ignoreonmainand on this branch alike. CI's mypy targetsmessagefoundry messagefoundry_webconsoleonly. My first comparison of this was unsound -- two files in one run against one in the other, which changes what mypy resolves; redone like-for-like.Not run: the full suite. No engine, web console or harness code is touched.
CI HAD NOT REPORTED WHEN THIS BRANCH WAS FINISHED. Do not read the table above as a green run. Every row in it was measured locally. At hand-off the rollup was 20 QUEUED / 7 skipped, unchanged for roughly 50 minutes, with 70 open PRs in the repository saturating the runners.
mergeable=MERGEABLE,state=BLOCKED-- the block is the queue, not a conflict. Nobody has seen this branch pass CI.Read on a hosted runner:
windows-service-smokeand the other runner-only legs.Closes BACKLOG #1462.
Generated with Claude Code