fix(pm): key dispatch-gates' dropped-declaration probe on the derived marker roster - #18856
Merged
Merged
Conversation
… marker roster `POPULATION_MARKER_LOOKALIKE` was built from `POPULATION_MARKER_KEYS` alone while the tool reads six reason-bearing keys, so `unparsedPopulationMarkers` was a probe over three of them and a silence over the other three: a `no-check-families`, `inherited-population` or `self-test-reads` line that READS as a declaration and does not PARSE produced exactly what a file that declares nothing produces. The lookalike is now keyed on `Object.keys(MARKER_REASON_GRAMMARS)` — the roster both grammar builders already derive — and each key's opener is the forms ITS language has, so on `no-check-families` a lookalike is a `#`-form line that fails to parse and a `//` line in a workflow stays document content. The refusal prescribes `markerFormsFor(key)` rather than the whole roster, so the remedy is one the author of that file can take. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
os-justin
marked this pull request as ready for review
September 18, 2026 00:58
os-justin
deleted the
claude/issue-18825-dropped-marker-probe-roster
branch
September 18, 2026 01:22
This was referenced Sep 18, 2026
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.
Fixes #18825
Clause-②: no
The defect
scripts/pm/dispatch-gates.mjsbuilt its dropped-declaration probe's pattern —POPULATION_MARKER_LOOKALIKE— out ofPOPULATION_MARKER_KEYSalone, while the tool readssix reason-bearing
dispatch-gates:keys. SounparsedPopulationMarkerswas a probe overthree of them and a silence over the other three: a
no-check-families,inherited-populationor
self-test-readsline that READS as a declaration and does not PARSE produced exactly what afile that declares nothing produces — no reason, no refusal, no row, no count. That is #18661's
own sentence, measured on the keys its repair never reached. The expensive member is
self-test-reads: a dropped declaration takes a family out of the derived set, and--ranthenrenders its zero-NOT-MEASURED verdict over a set that no longer contains it (#18673's hole, from
the parsing side).
The before-reading, re-derived at the tip
Taken through the exported
unparsedPopulationMarkersonorigin/main42f8df1723(the card'sown reading was on
ad1f94e8ec/05389952e; PR #18822 has landed on this file since, so everyline number and the roster were re-derived rather than inherited). One line each,
file=scripts/x.mjs:// dispatch-gates: no-path-population reason without separator(CONTROL)////-- dispatch-gates: no-path-population -- x(CONTROL, unknown opener)----# dispatch-gates: no-check-families reason without separator#// dispatch-gates: no-check-families -- x(a//line in YAML)// dispatch-gates: inherited-population a b reason//-- dispatch-gates: self-test-reads a -- x--The two controls are unmoved. The three shapes the old roster never reached now sound by file,
line, form and text. The
//-in-YAML shape stays silent on purpose — see below.The roster, derived
MARKER_LOOKALIKESis keyed onObject.keys(MARKER_REASON_GRAMMARS)— the roster PR #18822already derives from both grammar builders (
REASON_TAIL_MARKER_KEYS+PATH_LIST_MARKER_KEYS)— never a hand-list. A hand-list here would be a second copy of "which keys carry a reason that
can be dropped", and it would be wrong in the silent direction the moment a seventh key arrived:
the key would parse, and the probe would not know it existed. The self-test holds the two rosters
EQUAL, so neither can grow alone.
The per-key forms
markerLookalikeHead(key)is the counterpart ofmarkerLineHead(key)one question further out:that one is built from the forms the grammar ACCEPTS, this one from the forms the key's LANGUAGE
has. A key
MARKER_KEY_FORMSrestricts gets exactly its own openers; every other key keeps thewide punctuation class (
MARKER_LOOKALIKE_OPENER, unchanged bytes).So on
no-check-familiesa lookalike is a#-form line that fails to parse, and a//orblock-form line in a workflow is document content, not a dropped declaration — YAML has no
other comment, so a line spelled that way was never a comment to have been dropped out of.
Admitting one would report a line the workflow's own parser never treats as a remark, which is
the same reading error the grammar refuses one level up. A consequence stated in the docblock
rather than left to be found: on a restricted key only the missing-
-- REASONtail half of theprobe exists, because the unrecognised-comment-form half has nothing to find.
unparsedPopulationMarkerRefusalnow names that key's admissible forms per row(
markerFormsFor(key)), not the whole roster. A refusal that offered//as the repair for adropped workflow line would prescribe a spelling YAML has no comment for: the author would take
the advice, the line would parse as document content, and the declaration would still be dropped
— the same silence, now with the tool's endorsement.
The pins (all in this file's own
--self-test)MARKER_REASON_GRAMMARSasserted EQUAL — neither can grow alone;declaration in its own first admissible form, and every key stays silent on one that parses
— so a seventh key arrives probed;
//-and-block forms onno-check-familiespinned as NOT lookalikes, with the contrastbeside it (the same four forms on an unrestricted key are one row each) and the positive leg
(the
#form there DOES sound) — the restriction narrows which spellings are lookalikes, itdoes not empty them;
#and does not offer//; the script refusal names all five.The census, over the real tree
PR #18822's live census (the
.github/workflowsreader and thescripts/maskSelfTestsreader)now retains its corpus, and the LOOKALIKE half is taken over exactly those files:
Zero is the reading, not a dead instrument — three non-vacuity legs, each putting a dropped
line directly under a LIVE declaration so the corpus, the masking and the line numbering are the
real ones: a
#line with no separator under the liveno-check-familiesdeclaration is refusedby file and line and by
#as its remedy; a//line with no--separator under the liveself-test-readsdeclaration is found and nothing else is; and the SAME insertion spelled//in the workflow is found by neither reading. That last one is where a fix that keyed six keys on
the wide opener would go red.
This answers the card's
⛔ Not measured: no live file in this tree carries a dropped declarationon those three keys today.
The ablation
Leg 1 — the roster, from the committed fix, full suite
MARKER_LOOKALIKESput back onPOPULATION_MARKER_KEYSon disk, from the committed tree, undera
trap … EXIT INT TERMrestore:The FIRST attempt at this leg was a no-op — the mutation step and the anchor had two different
spellings of the same string and nothing reached the disk. It is recorded rather than quietly
retried: the on-disk guard refused it (
MUTATION FAILURE: the file on disk did not move — this ablation did NOT run, blob unmoved, exit 9) and the run below is the repaired one, with OLD andNEW single-sourced.
Ablated suite:
✗ dispatch-gates self-test: 7 of 1848 case(s) failed(lock VERDICTcommand-exit 1, held 742s). All seven are pins this PR adds; nothing pre-existing reds:The population CONTROL pin stays GREEN under the ablation, which is the point of it; so does the
census row itself (0 either way — that row is a liveness reading, and its three non-vacuity legs
are what red).
Leg 2 — the per-key FORM restriction (targeted reading, NOT a suite run)
Said by name: this leg is a probe-level reading through the exported function, not a run of the
1,848-case battery. The six-key roster kept, the per-key restriction dropped
(
const opener = MARKER_KEY_FORMS[key]→const opener = false), on disk from the committed fix,blob
260f6e85…→d88df903…, same trap:That is the triage's second blind spot, reproduced: a fix that widened the roster and kept the
wide opener reports two lines of workflow DOCUMENT CONTENT as dropped declarations. With the
restriction in place the first two are 0 rows and the third is 1 — the pin
「a
//or block-form line onno-check-familiesis DOCUMENT CONTENT …」 is what would red.Restored and verified by blob hash;
git diff HEADempty.Gates
Derived from the worktree with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no hand-fed path list), every derived command run, exit code capturedredirect-then-
$?, reconciled with--ran:node scripts/check-ci-filter-parity.mjsnode scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-testnode scripts/check-comment-mask-corpus.mjsnode scripts/check-declaration-mirrors.mjsnode scripts/check-declaration-mirrors.mjs --self-testnode scripts/check-scripts-symbol-anchors.mjsnode scripts/check-scripts-symbol-anchors.mjs --self-testnode scripts/check-self-test-wired.mjsnode scripts/check-self-test-wired.mjs --self-testnode scripts/check-self-test-workflow-commands.mjsnode scripts/check-self-test-workflow-commands.mjs --self-testnode scripts/check-whole-set-label-write.mjsnode scripts/check-whole-set-label-write.mjs --self-testpnpm check:agent-test-spellingpnpm check:bash32-floorpnpm check:cli-command-idspnpm check:cross-package-test-inputspnpm check:declared-population-livepnpm check:driver-memory-censuspnpm check:entry-guardpnpm check:nul-bytespnpm check:parse-guardpnpm check:pnpm-filter-targetspnpm check:ratchet-remedy-authoritypnpm check:refd-timer-probepnpm check:watch-hint-literalpnpm check:pm-dispatch-gates— the same run as the self-test line below, taken under the shared verify lock--ranreconciliation (exit codes recorded ascommand :: exit code,$?captured before anypipe):
28 derived famil(ies) accounted for — 28 run, 0 NOT-MEASURED (a DERIVED zero — all 28 recorded an exit code and none of them is 3), exit 0.Self-test line (detached, waited on with
tail --pid, never under a foreground timeout):✓ dispatch-gates self-test: 1848 cases pass— lock VERDICTcommand-exit 0, held 745s (12m25s).Repo-wide
pnpm lint(eslint . --no-inline-config): exit 0, no narrowing.Tier, re-derived in the worktree:
Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s).Coordination
@@ -4965,7 +4965,7 @@inCOMPOUND_ANCHOR_LEDGER— a one-line rename of ascripts/check-platform-checklist.mjsself-testsymbol. Read; not touched. This diff's lowest line is well above it, and adds no compound
selfTest-shaped name, so it needs no ledger row. Either PR lands first.Clause-②: noPR that touches no contract surface still owe an in-seat review before it can land? #18536's machine-side half is the next card on this file. Title read; not folded in.//and#only, so a declaration written inside a BLOCK comment parses as NOTHING — two live gates' examined-and-explained status is dropped with no tell #18661 / fix(pm): dispatch-gates reads a population declaration written in a block comment, and refuses one it cannot read #18784 are landed and read, not re-opened.Acceptance notes
skip-changeset: nothing published moves.scripts/pm/**is the fast lane — not in anypackage's
files[].Generated by Claude Code