Commit d099764
fix(pm): key dispatch-gates' dropped-declaration probe on the derived marker roster (#18856)
Fixes #18825
Clause-②: no
## The defect
`scripts/pm/dispatch-gates.mjs` built its dropped-declaration probe's
pattern —
`POPULATION_MARKER_LOOKALIKE` — out of `POPULATION_MARKER_KEYS` alone,
while the tool reads
**six** reason-bearing `dispatch-gates:` 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 — 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 `--ran` then
renders 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 `unparsedPopulationMarkers` on `origin/main`
`42f8df1723` (the card's
own reading was on `ad1f94e8ec` / `05389952e`; PR #18822 has landed on
this file since, so every
line number and the roster were re-derived rather than inherited). One
line each, `file` =
`scripts/x.mjs`:
| probe line | before | after |
|---|---|---|
| `// dispatch-gates: no-path-population reason without separator`
(CONTROL) | **1 row**, form `//` | **1 row**, form `//` |
| `-- dispatch-gates: no-path-population -- x` (CONTROL, unknown opener)
| **1 row**, form `--` | **1 row**, form `--` |
| `# dispatch-gates: no-check-families reason without separator` | **0
rows** | **1 row**, form `#` |
| `// dispatch-gates: no-check-families -- x` (a `//` line in YAML) |
**0 rows** | **0 rows** — document content, deliberately |
| `// dispatch-gates: inherited-population a b reason` | **0 rows** |
**1 row**, form `//` |
| `-- dispatch-gates: self-test-reads a -- x` | **0 rows** | **1 row**,
form `--` |
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_LOOKALIKES` is keyed on `Object.keys(MARKER_REASON_GRAMMARS)` —
the roster PR #18822
already 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 of `markerLineHead(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_FORMS` restricts gets exactly its own openers;
every other key keeps the
wide punctuation class (`MARKER_LOOKALIKE_OPENER`, unchanged bytes).
So on `no-check-families` a lookalike is a **`#`-form line that fails to
parse**, and a `//` or
block-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-`--
REASON` tail half of the
probe exists, because the unrecognised-comment-form half has nothing to
find.
`unparsedPopulationMarkerRefusal` now names **that key's** admissible
forms per row
(`markerFormsFor(key)`), not the whole roster. A refusal that offered
`//` as the repair for a
dropped 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.
```
.github/workflows/x.yml:1 declares no-check-families in form # — "..." (no-check-families may be written in: #)
scripts/x.mjs:1 declares wide-population in form ; — "..." (wide-population may be written in: //, #, /**, /*, *)
```
## The pins (all in this file's own `--self-test`)
- the lookalike roster and `MARKER_REASON_GRAMMARS` asserted EQUAL —
neither can grow alone;
- the two population controls, 1 row each, key and form named — unmoved
by this card;
- the card's own three shapes, each one row, by file / line / form /
text;
- derived over the roster rather than written out six times: **every**
key sounds on a dropped
declaration in its own first admissible form, and **every** key stays
silent on one that parses
— so a seventh key arrives probed;
- the `//`-and-block forms on `no-check-families` pinned as NOT
lookalikes, with the contrast
beside 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, it
does not empty them;
- the refusal's remedy is the key's own forms: the workflow refusal
names `#` and does not offer
`//`; the script refusal names all five.
## The census, over the real tree
PR #18822's live census (the `.github/workflows` reader and the
`scripts/` `maskSelfTests` reader)
now retains its corpus, and the LOOKALIKE half is taken over exactly
those files:
```
✓ no live file carries a DROPPED declaration on the three keys outside the population roster
(37 workflow(s) + 296 script(s) swept; found none)
✓ no live gate source carries a declaration this grammar drops, on any of the 6 reason-bearing keys
(250 source(s) swept)
```
**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 live
`no-check-families` declaration is refused
by file and line and by `#` as its remedy; a `//` line with no ` -- `
separator under the live
`self-test-reads` declaration 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 declaration
on those three keys today.
## The ablation
### Leg 1 — the roster, from the committed fix, full suite
`MARKER_LOOKALIKES` put back on `POPULATION_MARKER_KEYS` on disk, from
the committed tree, under
a `trap … EXIT INT TERM` restore:
```
HEAD blob : 260f6e8
on disk before : 260f6e8 (equal — the mutation is FROM the fix)
anchor occurrences 1 -> 0 · injected occurrences 0 -> 1
on disk after : 4028201a93c1369bdaeac6fa61449edffcbbaefb MUTATION LANDED
```
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 and
NEW single-sourced.
Ablated suite: `✗ dispatch-gates self-test: 7 of 1848 case(s) failed`
(lock VERDICT
`command-exit 1`, held 742s). **All seven are pins this PR adds; nothing
pre-existing reds:**
```
✗ the lookalike roster IS the derived grammar roster …
✗ and the three keys the old roster never reached now SOUND, each by file, line, form and text …
✗ every key in the roster sounds on a dropped declaration written in its own first admissible form …
✗ and the `#` form on `no-check-families` is the one that DOES sound …
✗ the refusal prescribes the forms of the KEY it names and not the whole roster …
✗ and that sweep is not vacuous: put a `#` line with no separator under the LIVE `no-check-families` declaration …
✗ and the same in a SCRIPT, on the path-list key: a `//` line under the LIVE `self-test-reads` declaration …
```
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).
```
RESTORE head-blob=260f6e85df6c62798248bdb3e0fb66e17edcf554 on-disk=260f6e85df6c62798248bdb3e0fb66e17edcf554
git diff HEAD: empty · git status --porcelain: empty
```
### 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:
```
1 row(s) :: // dispatch-gates: no-check-families -- x
1 row(s) :: /* dispatch-gates: no-check-families -- x
1 row(s) :: # dispatch-gates: no-check-families reason without separator
```
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 on `no-check-families` is DOCUMENT CONTENT …」
is what would red.
Restored and verified by blob hash; `git diff HEAD` empty.
## 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 captured
redirect-then-`$?`, reconciled with `--ran`:
| exit | command |
|---|---|
| 0 | `node scripts/check-ci-filter-parity.mjs` |
| 0 | `node scripts/check-closing-keyword-parity.mjs` |
| 0 | `node scripts/check-closing-keyword-parity.mjs --self-test` |
| 0 | `node scripts/check-comment-mask-corpus.mjs` |
| 0 | `node scripts/check-declaration-mirrors.mjs` |
| 0 | `node scripts/check-declaration-mirrors.mjs --self-test` |
| 0 | `node scripts/check-scripts-symbol-anchors.mjs` |
| 0 | `node scripts/check-scripts-symbol-anchors.mjs --self-test` |
| 0 | `node scripts/check-self-test-wired.mjs` |
| 0 | `node scripts/check-self-test-wired.mjs --self-test` |
| 0 | `node scripts/check-self-test-workflow-commands.mjs` |
| 0 | `node scripts/check-self-test-workflow-commands.mjs --self-test` |
| 0 | `node scripts/check-whole-set-label-write.mjs` |
| 0 | `node scripts/check-whole-set-label-write.mjs --self-test` |
| 0 | `pnpm check:agent-test-spelling` |
| 0 | `pnpm check:bash32-floor` |
| 0 | `pnpm check:cli-command-ids` |
| 0 | `pnpm check:cross-package-test-inputs` |
| 0 | `pnpm check:declared-population-live` |
| 0 | `pnpm check:driver-memory-census` |
| 0 | `pnpm check:entry-guard` |
| 0 | `pnpm check:nul-bytes` |
| 0 | `pnpm check:parse-guard` |
| 0 | `pnpm check:pnpm-filter-targets` |
| 0 | `pnpm check:ratchet-remedy-authority` |
| 0 | `pnpm check:refd-timer-probe` |
| 0 | `pnpm check:watch-hint-literal` |
| 0 | `pnpm check:pm-dispatch-gates` — the same run as the self-test
line below, taken under the shared verify lock |
`--ran` reconciliation (exit codes recorded as `command :: exit code`,
`$?` captured before any
pipe): `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 VERDICT
`command-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
- **PR #18840** (os-bill, open, another lane) edits this file at `@@
-4965,7 +4965,7 @@` in
`COMPOUND_ANCHOR_LEDGER` — a one-line rename of a
`scripts/check-platform-checklist.mjs` self-test
symbol. 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.
- **#18536**'s machine-side half is the next card on this file. Title
read; not folded in.
- PR #18822 / #18661 / #18784 are landed and read, not re-opened.
## Acceptance notes
- `skip-changeset`: nothing published moves. `scripts/pm/**` is the fast
lane — not in any
package's `files[]`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7607076 commit d099764
1 file changed
Lines changed: 322 additions & 29 deletions
0 commit comments