tooling(pm): key the bare-root worklist on the gate source file, folding the invocation twins - #15127
Merged
Merged
Conversation
…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
This was referenced Sep 4, 2026
os-steve
marked this pull request as ready for review
September 4, 2026 02:59
os-steve
enabled auto-merge
September 4, 2026 02:59
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
This was referenced Sep 4, 2026
Merged
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 #15091
A recorded verdict in
scripts/pm/bare-root-worklist.mjsis about a bare-root literal written in a gate's source, never about an invocation of it. The row key said otherwise: it wasfamily 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 nowsource-file constant word, and every family that resolves to the same source file folds into that one row, listed on it inchecks.scripts/pm/dispatch-gates.mjsis read and not touched: the source file behind a(script, args)family was already on the family entry (entry.files), whichsweep()was already iterating, so the fold needed no reading the derivation does not expose.The twin enumeration, measured at the merge base
f594e70dNine twin sets, 15 rows folding away — the nine
--self-testtwins 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.scripts/check-adr-0087-registration.mjs PACKAGE_ROOTS packagescheck:changeset-gate-self-tests·--self-test·--base "$MERGE_BASE"·--base "$SNAPSHOT_SHA"scripts/check-adr-0087-registration.mjs PACKAGE_ROOTS appsscripts/check-adr-0087-registration.mjs PACKAGE_ROOTS examplesscripts/check-declaration-mirrors.mjs SCRIPTS_DIR scripts--self-testscripts/check-position-name-fold-loaders.mjs SCAN_ROOTS packages--self-testscripts/check-position-name-fold-loaders.mjs SCAN_ROOTS examples--self-testscripts/check-position-name-fold-loaders.mjs SCAN_ROOTS apps--self-testscripts/check-position-name-fold-loaders.mjs SCAN_ROOTS scripts--self-testscripts/check-skills-token-ratchet.mjs SKILLS_DIR skills--self-test24 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.mjssets have four rows each, not three.check:changeset-gate-self-testsis 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 measuredwhy, 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
check:changeset-gate-self-testsfor adr-0087; the unflagged row for the other three files). Its verdict, spelling and reason stand byte-for-byte.--self-testgreen.checkson the row that already exists instead of arriving as a FRESH row to be judged again.coveredis 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.Self-test verdict lines, before and after
Before, at the merge base
f594e70d:After, at
66c65e6b: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):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 populationpin 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 to57cfe740…,git diff HEADempty). No build is involved: this script has nodist.check constant word)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 · FRESHconstant word)two different source files still owe their own rows — 1 together against the 1 + 1 literal(s) they hold apart· SPLIT · STALE · FRESHthe 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 othersGates, all run at head
66c65e6bExit codes captured before any pipe; each row quotes the gate's own verdict line.
node scripts/check-ci-filter-parity.mjsOK: all 143 declared cross-package glob(s) (99 unique) are covered by core or crosspkg…node scripts/check-closing-keyword-parity.mjscheck-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-testcheck-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-testOK 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-inputsOK: 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-bytescheck-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 238sover✓ 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-probeOK 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 nothingnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandswas 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-worklistfinds 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 ofmainbefore this PR (15 rows carried a manifests spelling atf594e70d) 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