fix(pm): a present-but-unreadable Seat: declaration reads as null, never as seat 1 - #19423
Conversation
…ever as seat 1 `claimSeatNumber` returned 1 whenever the line-initial key regex found nothing, whatever else the body carried. A claim declaring its seat mid-sentence — the same text, inside a code span, inside a subscript tag, or behind a `> - ` prefix — therefore read as a REAL seat, so the misread never surfaced as an error: it surfaced as an accusation, with H38 rowing another seat's post as stale over three claims of one round. A presence probe now runs only where the key line matched nothing: a body that carries the whole declared payload (`Seat:` then `domain:<lane>#<n>`) somewhere the key line cannot see is PRESENT-but-unreadable and returns null, matching no seat and staying invisible to the seat filter — the file's documented under-reporting direction. A body with no declaration at all still returns 1, which is what keeps every claim written before the line existed attributed to the only seat those lanes had. The probe deliberately does NOT widen `CLAIM_SEAT_KEY_LINE`: accepting a malformed declaration would leave the writer no way to find out either. It is case-sensitive and demands the lane-and-number payload so the pinned prose control keeps reading as seat 1. Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgments
② Semver levelNone — nothing published; ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #19108
Clause-②: no
claimSeatNumberinscripts/pm/check-half-states.mjsreturned 1 whenever its line-initial key regex found nothing, whatever else the body carried. A claim that declares its seat anywhere the key line cannot see — mid-sentence, inside a code span, inside a subscript tag, behind a> -prefix — therefore read as a real seat, so the misread never surfaced as an error: it surfaced as an accusation, and H38 rowed a different seat's post as stale over it.A presence probe now runs only where the key line matched nothing. A body carrying the whole declared payload (
Seat:thendomain:LANE#N) somewhere the key line cannot see is PRESENT-but-unreadable and returnsnull— matching no seat, invisible to the seat filter, which is this file's documented under-reporting direction. A body with no declaration at all still returns1, and the docblock now says why: that default is what keeps every claim written before the line existed attributed to the only seat those lanes had.⛔ The probe deliberately does not widen
CLAIM_SEAT_KEY_LINE. Accepting a malformed declaration would leave the writer no way to find out either — the same defect wearing the other costume — and the file already carries a maintainer ruling that the literalClaim:marker must not be widened. Refusing a malformed declaration at the writing path is a different instrument's job: #19152 is not addressed here.Before / after — the card's measured table, module imported directly
Stored comment bodies fetched through the REST proxy; controls verbatim from the card.
reprobefore the fix on5a5e710, after ona62389c.Seat:lines that the 2026-09-18 repair edit added)nullnull- **Seat**: …)null> - Seat:linenullnullnullSeat:lines the repair added and leaves the opening sentence's inline code span untouched — that sentence is unchanged prose in all three stored bodies, so what is measured is the original declaration shape, not a synthetic one. The card's other population — 「this seat's eight earlier claims of the same shift」 — was NOT MEASURED: enumerating them needs a board scan outside this run's read channel. The three as-stored bodies carry exactly that shape (line-initialSeat: domain:spec#3) and are measured instead, in the same direction.The pin that read
asserted 1. It now asserts
null, and the case name and its comment say so: blockquote-then-bullet is still no line-initial match, but the declaration is present, so the honest answer isnull. That row was itself an instance of the defect this card names — a present declaration silently answered with a real seat — so leaving it at 1 would have fixed the reported spelling and kept a measured sibling of it.PM mechanism assumptions — each verified, two corrected
claimSeatNumberis the only reader of theSeat:line.git grep 'claimSeatNumber'over the tree: 9 occurrences, all inscripts/pm/check-half-states.mjs(1 definition, 1 production caller, 7 self-test pins). Its only caller isnewestLaneClaim(:9257), whose only production call site is the H38 loop (:23996); the two self-test call blocks are the rest. A tree-widegit grep 'Seat'overscripts/and.github/names no second reader —.claude/skills/pm-dispatch/SKILL.mdcarries the template text, not a reader.seatLanereads the seat number off the post title, a different key.Re-read through the fix ⇒⚠️ Refuted: there is no existing H38 /
null, and the line-initial form still reads 3. See the measurement limit declared above on the eight-claim leg.3. ✅ Decided: a
nullseat is simply filtered out — no new H38 sentence. Reasoning below.4.
claimSeatNumberbattery to put cases in. The roster (SELF_TEST_BATTERIES, 8 entries) declares itself PARTIAL in its own header — 「the several hundredt(...)cases above and below are UNBATTERIED and stay that way here」 — and everyclaimSeatNumberpin is an unbatteriedt(...). So the new cases are unbatteriedt(...)beside them,SELF_TEST_BATTERY_FLOORstays 8, and no battery was opened. Every one of the eight batteries still registers above its pin.The H38 decision, on the four axes
The grading allowed either 「
nullor a named row」. Chosen:null, filtered out — H38 gains no sentence.foreignreasoning, quoted inh38SeatPostStale). Withnullthe claim contributes nothing and H38 declines by construction — the card's own shape 3, reached without new prose.Why the probe is case-sensitive and demands the whole payload
Off the line start there is no position left to tell a declaration from prose. The file pins 「the seat: domain:services#2 is busy」 ⇒ 1 as a deliberate control; a case-insensitive bare-key probe would flip it to
nulland start finding declarations in prose. So the probe requires a capitalSeat, a colon, and the lane-and-number payloaddomain:LANE#N. It is pinned to the measured shape, and its docblock says which direction to move it: a variant it misses falls back to 1, which is the defect, so widen on evidence — ⛔ never narrow it.Tests
node scripts/pm/check-half-states.mjs --self-test→ exit 0,5124 cases pass(base5a5e710:5118 cases pass⇒ the 6 new cases are counted; one existing case changed its expectation). All 8 batteries above their pins: 183/172 · 37/34 · 46/42 · 152/142 · 213/184 · 55/52 · 11/10 · 26/24.scripts/ablation-replace.mjsso the mutation is proven on disk: replacing the one changed production line with its oldif (!m) return 1;⇒anchor 1 -> 0, blob 9a8ba4b32ba3 -> 665c53ffe8ee, self-test exit 1,4 of 5124 case(s) failed— the mid-sentence case, the subscript-tag case, the flipped> - Seat:pin and the seat-1 leg of the end-to-end pair. Restore verified by the tool:blob == HEAD (9a8ba4b32ba3)andgit diff HEADempty. The other 3 new cases are controls that must stay green under that ablation (prose ⇒ 1, a lane token with no key ⇒ 1, no declaration ⇒ 1), and they did.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackata62389c(no paths passed — the script derives the change set itself, three-dot vs merge base5a5e710): 41 families, every one run with its exit captured before any pipe, 41/41 exit 0. Reconciled with--ran: 「41 derived, 41 run, 0 NOT-MEASURED, 0 UNRUN」.check:pm-label-desc-cap,check:pm-governed-prose,check-published-list-mirrors,check:pm-settings-deny-roster,check:select-gate-families— all exit 0.node scripts/pm/check-governed-merges.mjs⇒ NOT governed (scripts/pm/**is not on the register, 0 of 1 path hit it) — ordinary queue landing.skip-changeset:scripts/pm/**publishes nothing from any released package.Acceptance notes
Off-path observations, ⛔ not filed and ⛔ not fixed here (os-dev.md rule 3):
noted, not filed:the file's own H38 self-test fixtures are unbatteried, so the roster's eight batteries do not floor this row family at all; opening one is a diff of its own and the roster header already declares that gap deliberately. 承接者: the next card that adds an H38 row.noted, not filed:the sibling boards were never measured for the same misread — the card's own evidence limit. 承接者: none today; it needs a cross-repo sweep nobody is holding.Generated by Claude Code