Skip to content

fix(coord): the dispatch gate's two seat maps named retired seats (BACKLOG #1462) - #937

Open
wshallwshall wants to merge 5 commits into
mainfrom
claude/elastic-fermat-2d7bf2
Open

fix(coord): the dispatch gate's two seat maps named retired seats (BACKLOG #1462)#937
wshallwshall wants to merge 5 commits into
mainfrom
claude/elastic-fermat-2d7bf2

Conversation

@wshallwshall

@wshallwshall wshallwshall commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

What was wrong -- two constants, not one

judge() in dispatch_gate.py folds both seat maps straight into the line a dispatcher reads, so neither is documentation:

Verdict is 'owner-ruling' -- DO NOT JUST BUILD IT: <GATED_VERDICTS value>
closes by 'scorecard-rescore', performed by <CLOSING_SEAT value> -- NOT by the builder.
Constant Retired seats named
CLOSING_SEAT (backlog_status_check.py) ASVS Tracker, Liaison, Dispatcher -- in 3 of 4 values
GATED_VERDICTS (dispatch_gate.py) Liaison in both values, Dispatcher in one

Reach, measured in this checkout by calling judge() over each of origin/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. And grep -rn dispatch_gate .github/ .pre-commit-config.yaml returns 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 929 returns OPEN, mergedAt: null (updatedAt 2026-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 on main already grants standing vault authority, so the citation resolves either way. claude_section_check.py exits 0.

The routing changed, not just the seat's name

GATED_VERDICTS said 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-rescore names 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

Cited Claim Actually at
:253 "You may not conclude an item CLOSED" :142. Line 253 itself is BLANK, and filer -- the word the citation's prose used -- appears zero times in the file, with banner at 7 as the positive control.
:148 "The lander writes the banner" :216. Line 148 is now about ADR numbering.

Two independent reasons nothing here could fire: BUILDER.md lives in the vault roles/ folder, so no checkout of this repository carries it; and citation_line_check.py refuses 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.py is at tests/tooling_manifest.txt:84; tooling is in the needs: list of ci-gate; CI gate is required per .github/required-contexts.txt. All three verified in this checkout. Every constant and its assertions move together here, so CI gate cannot red on a half-applied rename.

Arms, and the mutations that earned them

Both arms walk both maps.

Mutation retired-seat arm names-nobody arm
CLOSING_SEAT regressed to the ASVS Tracker fires silent
GATED_VERDICTS regressed to the Liaison fires silent
a value stripped to name nobody silent fires

The 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 then GATED_VERDICTS after 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.yml and CLAUDE.md section 5's Builder row agree -- both put the docs/BACKLOG.md edit in the same PR that carries the code, and that PR is the Builder's. The source that disagrees is the vault roles/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 and claim.ps1 all 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_HEADING on the heading, _RETIRED_DECLARATION on the body -- which is what load_ledger passes after dropping the banner block), and judge() returns refuse, leading with "RETIRED IN PLACE -- DO NOT BUILD IT". The peer predicted advise; the measured level is stronger.

The risk this takes, named rather than left to ride

#1460 is not on main. It is on origin/builder-seat-name-filing behind #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 head c83e5650f, and it holds -- but the shape matters and the shorthand "937 conflicts" would mislead a Lander:

Pair Result
937 x origin/main clean, exit 0 -- and GitHub agrees: mergeable=MERGEABLE
937 x 936's branch CONFLICT, docs/BACKLOG.md

So 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 ## 1462 heading.

The resolution lives in this branch, but it is NOT mine to trigger. Once 936 merges, a rebase resolves it against a #1460 row 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-tree returns the same silence as a clean tree: the documented conflicting pair (worktree-agent-acdb9222ba84254fb x ...a01b6380fa19e85ea) exits 1, and a branch merged against itself exits 0.

Held, as instructed: rows 1122, 1169, 1182, 1188 (PR 935). docs/BACKLOG.md shows 0 deletions against origin/main across 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

Check Result
ruff check . / ruff format --check . pass, 1258 files
mypy messagefoundry (strict) pass, 268 source files
pytest ledger + coordination suites 209 passed
backlog_status_check.py rc 0 -- 672 items, each one status
claude_section_check.py rc 0 -- 774 citations, 306 files
link_check.py rc 0 -- 5,065 links
backlog_citation_check.py rc 0 (2 advisories, both pre-existing and unrelated)
pre-commit (via git commit) all wired hooks passed

One advisory, not mine. #1426 is open and unscored -- confirmed identical on origin/main with parse_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:115 reports an unused type: ignore on main and on this branch alike. CI's mypy targets messagefoundry messagefoundry_webconsole only. 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-smoke and the other runner-only legs.

Closes BACKLOG #1462.

Generated with Claude Code

wshallwshall and others added 3 commits September 5, 2026 19:38
`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>
@wshallwshall wshallwshall changed the title fix(coord): CLOSING_SEAT named three retired seats (BACKLOG #1462) fix(coord): the dispatch gate's two seat maps named retired seats (BACKLOG #1462) Sep 6, 2026
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>
@github-actions github-actions Bot added the unread Green and mergeable, but not marked read. Read it, then add: reviewed label Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

@wshallwshall

This pull request is green and nobody has marked it read.

Every check that has reported is passing and the reviewed label is absent, so
a reviewer has read this is the only thing between it and a merge. Nothing else reports that, which is
why this comment exists (BACKLOG #1413).

To clear it, IN THIS ORDER:

  1. If the branch is behind main, update it FIRST: gh pr update-branch --repo MEFORORG/MessageFoundry 937.
    That push fires synchronize, and synchronize REMOVES the reviewed label. Labelling
    before updating throws the label away and costs a round trip.
  2. Read the diff.
  3. gh pr edit 937 --repo MEFORORG/MessageFoundry --add-label reviewed

DO NOT TRIAGE THIS FROM mergeStateStatus. It returns one value with precedence, so BEHIND,
DIRTY and UNSTABLE each mask the missing check -- the requirement is invisible until you act on
something else. This check never reads that field; see scripts/ci/check_unread_prs.py.

A seat can find every pull request in this state in one call: gh pr list --label unread.
The label is withdrawn automatically once this pull request leaves the state.

The reviewed label is a PROCESS gate. It records that a step happened. It does not
establish that an independent party looked, and nothing here should be read as saying it does.

@github-actions github-actions Bot added ci-red A required check went red. Attribute it before retrying. and removed unread Green and mergeable, but not marked read. Read it, then add: reviewed labels Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-red A required check went red. Attribute it before retrying.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant