Skip to content

tooling(pm): key the bare-root worklist on the gate source file, folding the invocation twins - #15127

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-15091-worklist-source-file-key
Sep 4, 2026
Merged

tooling(pm): key the bare-root worklist on the gate source file, folding the invocation twins#15127
os-steve merged 1 commit into
mainfrom
claude/issue-15091-worklist-source-file-key

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #15091

A recorded verdict in scripts/pm/bare-root-worklist.mjs is about a bare-root literal written in a gate's source, never about an invocation of it. The row key said otherwise: it was family constant word, and since #14880 the derivation keys a family on (script, args), so a gate CI invokes two ways reached the identical literal — same file, same constant, same root — under two family keys and owed two identical rows. The key is now source-file constant word, and every family that resolves to the same source file folds into that one row, listed on it in checks.

scripts/pm/dispatch-gates.mjs is read and not touched: the source file behind a (script, args) family was already on the family entry (entry.files), which sweep() was already iterating, so the fold needed no reading the derivation does not expose.

The twin enumeration, measured at the merge base f594e70d

Nine twin sets, 15 rows folding away — the nine --self-test twins and the six value-bearing rows #15114 added, exactly as the dispatch expected. Every set agrees on verdict and spelling, which is why the fold re-decides nothing.

Folded row (new key) Rows before Invocations folded into it
scripts/check-adr-0087-registration.mjs PACKAGE_ROOTS packages 4 check:changeset-gate-self-tests · --self-test · --base "$MERGE_BASE" · --base "$SNAPSHOT_SHA"
scripts/check-adr-0087-registration.mjs PACKAGE_ROOTS apps 4 the same four
scripts/check-adr-0087-registration.mjs PACKAGE_ROOTS examples 4 the same four
scripts/check-declaration-mirrors.mjs SCRIPTS_DIR scripts 2 plain · --self-test
scripts/check-position-name-fold-loaders.mjs SCAN_ROOTS packages 2 plain · --self-test
scripts/check-position-name-fold-loaders.mjs SCAN_ROOTS examples 2 plain · --self-test
scripts/check-position-name-fold-loaders.mjs SCAN_ROOTS apps 2 plain · --self-test
scripts/check-position-name-fold-loaders.mjs SCAN_ROOTS scripts 2 plain · --self-test
scripts/check-skills-token-ratchet.mjs SKILLS_DIR skills 2 plain · --self-test

24 rows across those nine sets become 9: 15 fold away, 69 live rows become 54, and 61 recorded verdicts become 46.

One enumeration detail the card could not have known: the three check-adr-0087-registration.mjs sets have four rows each, not three. check:changeset-gate-self-tests is a pnpm script that runs that same source file, so it was already a fourth key on the identical literal — a twin that predates #14880 and that the labelled sections never named. That row is the survivor here: it carries the measured why, and the three argv rows that each said in their own words that they restated no measurement fold onto it.

The fold, and what it does not do

  • No row stranded. The survivor of every set is the row that already held the measurement (check:changeset-gate-self-tests for adr-0087; the unflagged row for the other three files). Its verdict, spelling and reason stand byte-for-byte.
  • No verdict re-decided. All nine sets agreed on verdict and spelling before the fold, measured; nothing had to be chosen.
  • Nothing goes STALE or MISSING. The recorded table shrinks by exactly the folded twins in this commit: 61 keys re-spelled or dropped, 46 remain, --self-test green.
  • The class stops growing with the workflows. A gate CI starts invoking a third way now joins checks on the row that already exists instead of arriving as a FRESH row to be judged again.
  • The two labelled twin sections are retired, and the reason is recorded once beside the new key in the TRIAGE docblock.
  • covered is ANDed over the folded invocations — a literal is reachable only when every invocation that reads it can be named for an arbitrary card under the root — and a new pin proves no folded row's invocations disagree today, so this rule decides nothing quietly.
  • The wide contrast sweep is re-keyed with it, one dedupe rule for the file. Its docblock claim ("roughly twice the rows across roughly twice the families") was re-measured and still holds in file terms: 119 pairs across 71 files against the restricted 54 rows across 34 files.

Self-test verdict lines, before and after

Before, at the merge base f594e70d:

OK  self-test: 69 live row(s), 61 unreachable as spelled, 61 recorded verdict(s) — none stale, none missing, none contradicted. …

After, at 66c65e6b:

OK  self-test: 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted. Each row is one literal in one gate source file: 69 invocation(s) of those literals fold onto them, 15 of them as twins of a row that already existed, and no folded row's invocations disagree about reachability. …

Rows 69 → 54; recorded verdicts 61 → 46; the invariant holds across the migration.

What the self-test now holds

Four new pins, all read against a baseline walked independently of the sweep's own dedupe (every (invocation, file, constant, word) tuple the derivation reaches, through the same predicates, with no dedupe of its own):

  1. The case — a gate invoked three ways (plainly, its self-test leg, and a base a workflow fills in) owes ONE row per literal, and that row names all three invocations.
  2. The control — two different source files still owe their own rows. Its second file is chosen to share a constant and a root with the first wherever the tree offers such a pair, since that is the pair a key without the file collapses.
  3. The shrink, as a measured identity — the tuples the derivation reaches equal the rows plus the twins folded onto them. No number is typed: it fails if the fold invents a row, loses one, or drops an invocation off the row it folded into.
  4. The reachability rule — no folded row's invocations may disagree about coverage.

Fixture families are spliced over files taken from the tree, never spelled, so the file still declares no population of its own — the existing this tool declares no population pin is green.

An earlier draft of the control read its fixture off the live rows and passed a mutation that dropped the file from the key, because that mutation had already collapsed the pair the fixture was choosing itself from. That measurement is why the baseline is walked independently, and it is recorded in the comment above the block.

Ablations — each pin driven to red, on disk, then restored

Committed first, so the restore leg had a real reference. Each leg: mutate, prove the mutation landed (removed-text count 0, injected-text count 1, blob hash moved off the HEAD blob), run, restore with git checkout HEAD -- at the file's absolute path, prove the restore (blob hash back to 57cfe740…, git diff HEAD empty). No build is involved: this script has no dist.

Ablation Self-test Which pins red
Key reverted to the family (check constant word) exit 1, 5 failures a gate invoked three ways owes ONE row per literal, not three — 3 row(s) for the 1 literal(s) that file holds · the surviving row NAMES all three invocations · the fold judges something · STALE · FRESH
File dropped from the key (constant word) exit 1, 4 failures two different source files still owe their own rows — 1 together against the 1 + 1 literal(s) they hold apart · SPLIT · STALE · FRESH
Invocation not recorded on the folded row exit 1, 3 failures the fold is exactly a fold: 69 (invocation, file, constant, word) tuple(s) reach 54 row(s), and the 0 twin(s) folded onto them account for the whole difference · the two others

Gates, all run at head 66c65e6b

Exit codes captured before any pipe; each row quotes the gate's own verdict line.

Gate Verdict line
node scripts/check-ci-filter-parity.mjs OK: all 143 declared cross-package glob(s) (99 unique) are covered by core or crosspkg…
node scripts/check-closing-keyword-parity.mjs check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s)…)
node scripts/check-closing-keyword-parity.mjs --self-test ✓ check-closing-keyword-parity --self-test: 24 assertions, 5 mutations of the shipped parsers each driven to red.
node scripts/check-comment-mask-corpus.mjs ✓ comment-mask corpus sweep: 5853 files, 0 disagree, 0 unparseable, 41.7s (comparator self-test: 12 cases pass).
node scripts/check-self-test-wired.mjs ✓ check-self-test-wired: every one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CI.
node scripts/check-self-test-wired.mjs --self-test check-self-test-wired --self-test: 4 live ledger row(s) verified… 7 declared batteries, 50 cases registered, every battery at or above its pinned floor.
node scripts/check-whole-set-label-write.mjs ✓ check-whole-set-label-write: 0 violations — 257 file(s) over 3 root(s)…
node scripts/check-whole-set-label-write.mjs --self-test ✓ check-whole-set-label-write --self-test: all cases pass (24 fixture trees + 5 refusals + 1 allowlist hatch)
node scripts/pm/bare-root-worklist.mjs --self-test OK self-test: 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted…
pnpm check:agent-test-spelling ✓ check-agent-test-spelling: 0 violations — 437 file(s) · 5908 bare -- token(s) · 1404 launcher-rooted run(s)…
pnpm check:bash32-floor ✓ check-bash32-floor: 26 tracked shell file(s)… name no bash 4+ construct outside a comment, a guarded read, or a non-command position.
pnpm check:cli-command-ids ✓ check-cli-command-ids: 324 command-id literal(s) across 114 file(s) outside packages/cli all resolve to a real command path…
pnpm check:cross-package-test-inputs OK: 26 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
pnpm check:entry-guard ✓ check:entry-guard: 206 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 153 export bindings, 153 of them inert on import…
pnpm check:nul-bytes check-nul-bytes: OK (scanned 8226 text file(s)… no raw ASCII control bytes).
pnpm check:parse-guard ✓ check:parse-guard: 205 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
pnpm check:pm-dispatch-gates (through the shared lock) os-verify-lock: VERDICT command-exit 0 · held the lock 238s over ✓ dispatch-gates self-test: 1338 cases pass.
pnpm check:pnpm-filter-targets ✓ check:pnpm-filter-targets: 142/181 --filter occurrence(s) across 33 file(s) resolve against 79 workspace package(s)…
pnpm check:refd-timer-probe OK check-refd-timer-probe: 5848 source file(s) swept…
pnpm check:watch-hint-literal ✓ check-watch-hint-literal: 48 declaration(s) across 4 rostered name(s)… every one an array of quoted literals inside its own statement.
pnpm lint (repo-wide ESLint, through the shared lock) os-verify-lock: VERDICT command-exit 0 · held the lock 61s — eslint printed nothing

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands was re-derived from this worktree after the commit (gate list derived from the tree of 'objectstack-ai/objectstack' at commit 66c65e6b) and returns exactly the dispatched list — no family the brief missed. No test file in the tree pins this script (git grep -l bare-root-worklist finds only prose references and the lint workflow's own invocation), so there is no sibling suite owed beside the derived family.

Carried whole, and the one count this pass did not restate

The surviving rows' reasons are carried byte-for-byte. One of them — scripts/check-adr-0087-registration.mjs PACKAGE_ROOTS packages — contains the sentence "nine rows across three gates share this one population shape … a nine-edit expansion ahead of demand". That count was already false of main before this PR (15 rows carried a manifests spelling at f594e70d) and is 6 rows across 2 source files after the fold. It is not restated here: rewriting a deferral reason inside a row this PR is only re-keying would be a re-decision on a shrink-only map, which this pass is not authorised to make. Reported to the PM as a finding instead.

skip-changeset: scripts/pm/** publishes nothing.


Generated by Claude Code

…ing the invocation twins

A recorded verdict in this worklist is about a bare-root literal written in a
gate's SOURCE, never about an invocation of it, so the row key is now
(source file, constant, root) and every family the derivation keys on
(script, args) that resolves to the same source file folds into that one row,
listed on it in `checks`.

Fifteen rows fold away: the nine `--self-test` twins the previous key minted
when CI began invoking a gate two ways, and the six value-bearing rows that
arrived once the derivation started rendering the argv a workflow fills in.
Every one of them carried its twin's verdict and spelling and, in its own
words, no measurement of its own; each folds onto the row that holds the
measurement, whose verdict, spelling and reason stand unchanged. The two
labelled twin sections are retired and the reason is recorded once beside the
new key. The class also stops growing with the workflows: a gate CI starts
invoking a third way now joins `checks` on a row that already exists instead of
landing as a FRESH row to be judged again.

`covered` is ANDed over the folded invocations — a literal is reachable only
when every invocation that reads it can be named for an arbitrary card under
the root — and the self-test pins that no folded row's invocations disagree
today, so the fold re-decides no row's reachability.

The self-test grows four pins: a gate invoked three ways (plainly, its
self-test leg, and a base a workflow fills in) owes ONE row that names all
three; two different source files still owe two rows; the shrink is asserted as
a measured identity — the invocation tuples the derivation reaches equal the
rows plus the twins folded onto them — rather than a typed number; and no
folded row's invocations may disagree about reachability. Fixture families are
spliced over files taken from the live sweep, never spelled, so this file still
declares no population of its own.

`dispatch-gates.mjs` is read and not touched: the source file behind a
(script, args) family was already on the family entry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
@github-actions github-actions Bot added the size/m label Sep 4, 2026
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 4, 2026
@os-steve
os-steve marked this pull request as ready for review September 4, 2026 02:59
@os-steve
os-steve enabled auto-merge September 4, 2026 02:59
@os-steve
os-steve added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit 8f68e50 Sep 4, 2026
36 checks passed
@os-steve
os-steve deleted the claude/issue-15091-worklist-source-file-key branch September 4, 2026 03:40
os-steve pushed a commit that referenced this pull request Sep 4, 2026
…oss two source files

The `why:` on the `scripts/check-adr-0087-registration.mjs PACKAGE_ROOTS
packages` row priced its deferral at "nine rows across three gates". That
figure counted CI invocation names rather than gate source files, and the tree
has not matched it since the derivation grew a second key: #15127 keyed this
table on the gate source file and folded the invocation twins away.

Measured at the merge base, six rows in the recorded triage carry a manifests
spelling — three roots each under `scripts/check-adr-0087-registration.mjs` and
`scripts/check-skill-compatibility-version.mjs`. Two source files, not three
gates: `check:changeset-gate-self-tests` runs
`check-adr-0087-registration.mjs --self-test`, so it never was a third gate.

Only prose inside that one `why:` moves. The verdict, the spelling, the row key
and both population figures (74 of 5275, 74 of 74) are untouched — the pins
re-measure those — and `node scripts/pm/bare-root-worklist.mjs --self-test`
prints a byte-identical verdict line before and after the change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants