Skip to content

fix(pm): dispatch-gates reads a population declaration written in a block comment, and refuses one it cannot read - #18784

Merged
os-justin merged 3 commits into
mainfrom
claude/issue-18661-population-marker-block-comment
Sep 17, 2026
Merged

os-justin merged 3 commits into
mainfrom
claude/issue-18661-population-marker-block-comment

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #18661

Clause-②: no

scripts/pm/dispatch-gates.mjs's three population markers (no-path-population, whole-tree-population, wide-population) built their grammar from one head whose comment-form alternation listed // and # and nothing else, so a declaration written in a file's own BLOCK-comment idiom parsed as NOTHING — not refused, not printed, not counted, and therefore byte-identical in every channel this tool has to a gate that declares nothing at all. Two live gates were writing one. The author of each believed they had explained why their gate reads no population; every reader of the residue saw their family in the unexamined pile; neither side had anything to check against, which is what makes this class expensive rather than merely wrong.

The two before-readings (re-derived on this branch, not taken from the card)

Taken on origin/main 95b21b33be before any edit:

  1. The population reader answers null over both files. declaredNoPathPopulation returns null for scripts/symbol-anchors.mjs and for scripts/release-verify-npm.mjs, and populationReasonContinuation returns null for both as well — so not even the [finding] dispatch-gates' population markers capture only the FIRST line of a declaration's reason, and no refusal checks the reason is whole — a wrapped reason reaches a seat cut off mid-sentence, with nothing red #18422 wholeness refusal had anything to grade. Nothing was refused because nothing was read.
  2. Both families sit in undetermined with hints=0 and no annotation. node scripts/pm/dispatch-gates.mjs --residue --repo objectstack-ai/objectstack scripts/pm/dispatch-gates.mjs placed 309 families — 23 matched, 43 undetermined, 225 silent, 7 always-runs, 11 declared-wide — and reported 9 of those 43 undetermined famil(ies) DECLARE that they have no path population. node scripts/release-verify-npm.mjs --self-test and node scripts/symbol-anchors.mjs --self-test are both printed in the undetermined block with no names: suffix and no ↳ declared no path population line under them.

One line number on the card was stale and is corrected here: scripts/symbol-anchors.mjs's declaration is at :207 on today's tip, not :180; the text is unchanged. scripts/release-verify-npm.mjs:110 is where the card says.

① The grammar: one roster, five forms, and where a reason ENDS in each

MARKER_COMMENT_FORMS replaces the inline alternation. It is the single place a form is added, the head both marker builders and the continuation reading are derived from, and it classifies each form into one of two KINDS, because the two kinds answer the wholeness question differently:

kind forms where the reason ends
line //, # on the marker line. A comment line under it in the SAME form, carrying text that is not a new dispatch-gates: key, is a CUT and is refused — #18422, unchanged byte for byte
block /**, /*, * at the first of: the closing delimiter, a blank star-only line, the next star-@tag line, another dispatch-gates: key, or EOF. The star-prefixed lines between are JOINED into the reason

The block form's own wholeness question is answered in the header rather than left to be found. Two adjacent // lines are two separate comments and nothing in the text says whether the second belongs to the first — that is why #18660 refuses there, and that refusal is untouched. A /* ... */ block is ONE comment whose internal newlines are formatting, so its next star line is a continuation by construction and the join is decidable from the text rather than guessed. What the block form cannot do is cross any of the three places a block-comment author signals a new thought; each is pinned. A line INSIDE the block carrying text with no star prefix is none of the five endings — it is reason text the walk cannot read — so it is recorded as the block form's CUT and refused with its own remedy ("give that line the block's star prefix, or end the reason before it with a blank star line"), never with the line forms' advice, which would send that author to the wrong half of their declaration.

The one residual asymmetry is named in the header rather than hidden: a second sentence on the very next star line, with no blank line between, IS swallowed. That is OVER-inclusion and it reaches a seat as a reason that says too much — visible on the row. The truncation #18422 refused is UNDER-inclusion and reaches a seat as a sentence that merely ends oddly — invisible. The block idiom's own paragraph break is the text that separates the two, and it is what a block-comment author already writes.

⛔ What a population declaration MEANS is unchanged, and no refusal PR #18660 added is loosened: the line forms' capture, their continuation reading and their refusal text are all identical, and a self-test case asserts that refusal text still reads "Put the WHOLE reason on the marker line".

② The sound: a line that reads like a declaration and did not parse is never silent

unparsedPopulationMarkers + unparsedPopulationMarkerRefusal, RED at author time through this file's own --self-test live half, which sweeps every gate source the discovery reads (250 sources on this tree) and names each finding by FILE, LINE and the FORM it was written in — exactly the three things the old output withheld. Two causes, deliberately one finding, because the author's experience is identical: an unrecognised comment form, and a recognised form with no -- REASON tail.

The chosen place is the refusal rather than the derivation output, because the refusal fires before any row can be printed: an unparsed declaration cannot reach main, so a second rendering of a state that cannot exist would be unreachable code, which this file's own rule refuses.

Two boundaries, both measured rather than assumed. A line may carry AT MOST ONE comment opener — a line with the docblock's own star plus a second opener is an EXAMPLE of a declaration written inside a comment about declarations, which is how every example in this file is written, and the grammar reads one opener too, so probe and grammar agree about what documentation looks like. And a QUOTE is not a comment opener: every self-test in this family builds its fixtures out of string literals, this file's own included, and the grammar already ignores them for the same reason.

Census — the whole tree, report-only, no state written

Swept over all 8842 tracked files at 95b21b33be:

reading count
lines carrying the literal text dispatch-gates: 137
of those, lines carrying a POPULATION key 69
parse as a declaration under the OLD form set (//, #) 25, across 25 files
parse under the NEW form set (+ /**, /*, *) 27, across 27 files
NEWLY parsing 2 — scripts/release-verify-npm.mjs:110, scripts/symbol-anchors.mjs:207
declaration-SHAPED and unparsed on main (what ② would have flagged) 2 — the same two
still unparsed after ① 0

So ②'s only live cases were the two ① repairs, and the census is its coverage statement: the remaining 67 population-key lines are prose mentions (5 of them, all backticked or mid-sentence), this file's own docblock examples, and string-literal fixtures — none is declaration-shaped, and the live sweep over the tip returns zero. The pins for ② are therefore synthetic plus one live non-vacuity leg that takes a real declaration, re-spells its opener in an unlisted form, and asserts exactly that line is found.

The family before/after — nothing else moves

Same command, same probe path, before at 95b21b33be and after at 98d0b1dca4; the full listings differ by 28 lines and every movement is named:

  • + ↳ declared no path population — ... under node scripts/release-verify-npm.mjs --self-test — this change
  • + ↳ declared no path population — ... under node scripts/symbol-anchors.mjs --self-test, with the whole six-line reason joined — this change
  • 9 of those 43 undetermined becomes 11 of those 43 — this change
  • check-dev-prereqs 70 declared literals becomes 71, and its names: list grows — NOT this change: scripts/check-dev-prereqs.mjs moved on origin/main in the merge this branch carries
  • 8842 tracked file(s) becomes 8844 (two places) — NOT this change: the merge added .changeset/amplifiers-linked-packages.md and scripts/measure-markdown-ts-blocks.mjs
  • the derivation header's commit sha

The five bucket counts are byte-identical: 23 matched, 43 undetermined, 225 silent, 7 always-runs, 11 declared-wide. No family changed bucket. no-path-population annotates WITHIN undetermined rather than moving a family out of it, which is what the channel does.

The ledger row the readable declaration graduates

ROOT_WALK_RESIDUE_LEDGER carried a hand-written row for scripts/symbol-anchors.mjs --self-test — a repo-root walker that declares neither marker and cannot be placed by path. It declared neither marker only because its declaration could not be read. With the form set widened it leaves that population by DECLARING, which the table's own header names as the outcome it exists to push toward, and check:pm-dispatch-gates reds on a stale exclusion by design. The row is deleted in the same landing, with a ⚖️ note in the style the table already uses for its one previous graduation. This is the card's defect priced in a second currency: it had cost a hand-maintained exclusion row, carrying by hand the reading the gate's own source already carried.

Ablation (from the committed fix, restored under a trap)

Mutation: the three kind: 'block' rows deleted from MARKER_COMMENT_FORMS, proven on disk by blob hash — e72c4d48d33ef670c7baa16f3929cf3f4bf958f5 (== the HEAD blob) becomes 1c2128d800e60ffa93c81cbad76313c381f69c92.

ablated reading result
declaredNoPathPopulation over the two live files both back to null
residue's documented-no-population count 11 back to 9
pnpm check:pm-dispatch-gates exit 111 of 1809 case(s) failed
the 8 block-form grammar pins all RED
the live pin "the live tree's BLOCK-form declarations are READ and not dropped" RED, found 0: none
the SOUND, over the real tree RED, and it names both specimens: scripts/release-verify-npm.mjs:110 declares no-path-population in form * and scripts/symbol-anchors.mjs:207 declares no-path-population in form /*
the ledger case RED in the OPPOSITE direction — unlisted: scripts/symbol-anchors.mjs --self-test — so the row's deletion is coupled to the fix by construction
the other 1798 cases green, unchanged

Restore: git checkout HEAD -- scripts/pm/dispatch-gates.mjs under an EXIT INT TERM trap, verified by hash equality with the HEAD blob AND an empty git diff HEAD, not by an exit code.

Self-test — measured, not NOT MEASURED

pnpm check:pm-dispatch-gates run DETACHED with its output to a file and read from the file, never under a foreground timeout: exit 0, ✓ dispatch-gates self-test: 1809 cases pass, battery 748.0s on this box, at 71aacfb886.

Derived gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack from the worktree, no hand-fed path list: 1 path in the change set, 28 commands derived. Every one run, each exit code captured by redirect-then-$?, and reconciled:

✓ dispatch-gates --ran: 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)

All 28 exit 0. Repo-wide pnpm lint (eslint . --no-inline-config): exit 0. The derivation at 71aacfb886 prints a STALE TREE note naming one file that moved on origin/main afterwards, scripts/pm/post-stamped.mjs; the derived command list is byte-identical before and after that move, and the queue rebuilds this PR on the current main regardless.

Scope

#18662 is NOT folded in. It asks for the REASON-WHOLENESS reading (the cut refusal) to be extended to no-check-families and inherited-population; this change gives neither of them one, and NO_CHECK_FAMILIES_MARKER is a separate #-only regex for YAML that is untouched. One interaction is worth recording: pathListMarkerPattern shares the one head with the population markers by construction (#18673), so inherited-population and self-test-reads now also accept the block spellings. There are zero live block-form declarations of either key — both live ones are // — so nothing moves, and their reason-wholeness exposure is exactly what #18662 describes, neither widened nor narrowed here.

skip-changeset: the diff is one file under scripts/pm/**, a PM loop tool that no package's files[] ships.


Generated by Claude Code

…a block comment, and refuse one it cannot read

The three population markers' form alternation listed `//` and `#` only, so a
declaration written in a file's own block-comment idiom parsed as NOTHING — not
refused, not printed, not counted. Two live gates were writing one:
`scripts/symbol-anchors.mjs` (a slash-star opener, reason wrapped over six
lines) and `scripts/release-verify-npm.mjs` (a star-prefixed line inside a
docblock). Both read back `null` and both sat in the residue's `undetermined`
bucket with `hints=0`, indistinguishable from a gate whose population nobody
ever examined.

Two halves:

- the form roster gains the three block spellings (`/**`, `/*`, `*`) in ONE
  place, and answers the block form's own wholeness question: inside a block the
  star lines under a declaration are the SAME comment, so they are joined into
  the reason, and the reason ends at the closing delimiter, a blank star line,
  the next star-@tag line, another `dispatch-gates:` key, or EOF. A line with
  text and no star prefix is the block form's CUT, refused with its own remedy.
  The line forms' capture and #18422's cut refusal are unchanged, byte for byte.

- a line that READS as a population declaration and did not PARSE as one is
  refused by name — file, line and the form it was written in — over every gate
  source the discovery reads, so the next unlisted comment form, and a
  declaration whose reason someone forgot to write, cannot replay this silently.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
…on graduates

`scripts/symbol-anchors.mjs --self-test` was listed as an unnamed repo-root
walker because its own `no-path-population` declaration, written in a block
comment, read back `null`. With the form set widened the family leaves that
population by DECLARING — the outcome the table exists to push toward — so the
row is stale, and `check:pm-dispatch-gates` reds on a stale exclusion by design.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
@os-justin os-justin added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 17, 2026 — with Claude
@os-justin
os-justin marked this pull request as ready for review September 17, 2026 21:04
@os-justin
os-justin added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 7572329 Sep 17, 2026
37 checks passed
@os-justin
os-justin deleted the claude/issue-18661-population-marker-block-comment branch September 17, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants