Skip to content

fix(scripts): reconcile the dual-build ledger in the orphan direction, floor its vacuous pass, and correct what the sibling gate claims about its own failure mode - #13050

Merged
os-elon merged 2 commits into
mainfrom
claude/issue-13014-vacuous-extractor-class
Aug 29, 2026
Merged

fix(scripts): reconcile the dual-build ledger in the orphan direction, floor its vacuous pass, and correct what the sibling gate claims about its own failure mode#13050
os-elon merged 2 commits into
mainfrom
claude/issue-13014-vacuous-extractor-class

Conversation

@os-elon

@os-elon os-elon commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Part of #13014

⛔ Not a closing keyword on purpose: this PR discharges the code half of the card. The
naming half is blocked on a maintainer ruling (see "What is NOT in this PR"), and the
class decision the card asks for is stated below rather than merged away.

TL;DR — what the measurement says

the card's claim verdict evidence
the two instances share a mechanism falsified they are different defects that look alike
instance 1 (check-stack-collection-maps) passes vacuously on an empty extraction falsified that floor has existed since #7032; the counterfactual exits 1
instance 2 (check-dual-build-cjs-loads) never revisits a ledger key that left the population confirmed, live orphan row on the real tree: exit 0, pass line byte-identical, id unmentioned
the remedy generalises yes, but not as the card frames it two constructions do generalise; the card's own wording covers neither instance

1. Instance 1 is not the mechanism the card describes

The card defines the class from this gate's own header: "the two extractors this gate
already had ... could not read it and the site was skipped instead of failing ... Reading
a tuple site with the flat extractor returns an empty list at depth 0 — which reconciles
against everything and reports no drift."

Re-measured on 8cb96ec41b. The empty-extraction floor the card proposes as the remedy
already exists in that gate, and predates the incident:

$ git log -S 'siteKeys.length === 0' -- scripts/check-stack-collection-maps.mjs
3de535b860 2026-08-09 fix(spec,metadata): three self-descriptions re-anchored ... (#7032)

The counterfactual, run for real — swap the SECURITY_FIELDS extractor back to the flat
one and see what the gate does:

-- anchor counts AFTER: tupleFirstItems=0 (want 0)  stringArrayItems=5 (want 4)
-- hash before=6ac93cbd94c5... after=c32ec0ba3130...      (mutation landed on disk)
MUTATED_EXIT=1
✗ check-stack-collection-maps — 1 unreconciled deviation(s):
  • SECURITY_FIELDS — could not extract the enumeration from packages/runtime/src/app-plugin.ts.
    An unextractable site is a FAILURE, never a pass: an empty list reconciles against everything.

⇒ A tuple site read with the flat extractor goes red by name. It was never capable of
the vacuous pass the card attributes to it.

What actually happened: SECURITY_FIELDS was never a SITE. Probed on the gate as it
stood before #13009SECURITY_FIELDS 0 occurrences, positive control
ARTIFACT_FIELD_TO_TYPE 5 — so no extractor ever ran on it and there was nothing to
come back empty. The hole was the hand-written SITES population, which is answerable to
nothing. Same visible symptom, different defect, different remedy.

The gate's header now says so, with the measurement, because the wrong version of that
sentence is what the class was derived from and a reader who believes it concludes the
floor is missing and adds a second one.

2. Instance 2 is real, and this PR closes it

The only read of the ledger was ledger[r.id] from inside the walk over discovered
rows
. A key no row names is never looked up, and a lookup that never happens cannot
report. Measured on 8cb96ec41b, before the change — an invented exemption for a package
that does not exist:

ORPHAN_RUN_EXIT=0
✓ check:dual-build-cjs-loads — 103 published require entry point(s) across 67 package(s)
  load; 613 emitted CommonJS file(s) parse; 1 cross-format behaviour probe(s) agree.
-- does the output mention the orphan? 0

Exit 0, pass line byte-identical to the clean run, the id unmentioned. Added:

  • the ledger→population direction (orphanLedgerRows), a separate pass over
    Object.keys(ledger) precisely because the orphan direction is unreachable from the row
    walk. The same file already had this right one invariant over — runBehaviourProbes
    refuses a probe naming an entry point that no longer exists — so the ledger was the odd
    one out, not a new idea;
  • four vacuity floors (entries, publishable packages, emitted CommonJS files,
    behaviour probes run), each measured on 8cb96ec41b and held with margin. Below any of
    them the gate REFUSES with exit 2, a code distinct from findings (1) and
    prerequisite (3), so no if cmd reading can turn "read nothing" into a pass;
  • the header's "reconciles in both directions" sentence, which the card cites as false,
    rewritten to state the two stale directions and the measurement.

3. Ablation — predicted before running, with a green control

Prediction stated first, then the run. All three legs matched. The gate is executed as
node scripts/check-...mjs, so the mutated source is the executed artifact — no build
step sits between the edit and the run — and each leg proves its mutation landed by
object-hash change plus a grep count of the injected and removed text.

leg predicted observed
orphan ledger row on the real tree exit 1, names the id LEG1_EXIT=1, names @objectstack/no-such-package#./ghost and "exempting nothing"
GREEN CONTROL — a ledger row naming a live entry orphan pass silent; the pre-existing stale direction fires instead orphan wording 0 hits, stale wording 1 hit — the new pass discriminates rather than firing on everything
floor wiring — break the population walk exit 2 refusal, never 0 LEG3_EXIT=2, "measured only 0 published require entry point(s), below the floor of 90 (103 on 8cb96ec)"

Restore proved on both mutated files by object-hash equality against their HEAD blobs and
a zero-count grep for the mutant text; every mutation script carried a trap ... EXIT INT TERM restoring absolute paths resolved from git rev-parse --show-toplevel.

Plus, in --self-test (53 cases, up from 43): each floor driven to zero individually; the
measured tuple asserted to clear every floor (a floor accidentally set above its own
measurement would red every real run — the opposite failure, equally invisible); the
orphan case with two green controls; and the shipped ledger reconciled against the real
population.

4. Does the remedy generalise? Yes — but the card's wording covers neither instance

The general shape is not "an extractor that returns nothing". It is a lookup or a
sweep that comes back empty must refuse, never report clean
, and it has exactly two
enforceable expressions, both already worked in this tree:

  1. Vacuity floors on every count a gate's pass line publishes — check-keyed-text-bounds.mjs
    (five), check-undeclared-dep-imports.mjs (three), and now this gate (four).
  2. Both-direction reconciliation of every hand-maintained list against the discovered
    population. Everyone writes population→ledger; ledger→population is the one that goes
    missing. check-stack-collection-maps already had it for waivers (staleWaivers);
    check-dual-build-cjs-loads had it for probes but not for its ledger.

And a measured negative — the honest half. Instance 1's real mechanism (a hand-written
population with no discoverable counterpart) is not expressible by either. I measured
the obvious mechanical remedy — sweep the tree for literals naming many stack collections
and flag any that is not a pinned site: at a threshold of 8+ distinct collection keys it
returns 45 files, of which 38 are not sites (tests, example configs, generated files,
the conversions registry, i18n helpers). An allowlist of 38 relocates the hand-written list
rather than deleting it. So for that half the honest answer is a hand-written population
that says so where it is declared — which the gate's header now does.

Not two local repairs, and not one pattern either: one pattern with two expressions,
plus one instance the pattern does not reach.
That distinction is the card's real
product, and it is why the card should not be read as "add empty-extraction floors
everywhere".

5. On the two non-gate instances registered in triage

Triage's boundary held: #13031 and #13036 were not taken on here. Reading them against
the measurement above: both are the lookup expression, not the extractor one — a walker
that knows one of three legal spellings returns an empty walk for the other two, and a
ledger whose four checks are all satisfied by a tombstone has a key-name lookup that
matches the wrong thing. Expression 2 (reconcile in both directions, and refuse an empty
match) is the one that reaches them. Stated as evidence for whoever grades them, not as a
claim on them.

What is NOT in this PR, and why

The pattern is named once, in scripts/check-dual-build-cjs-loads.mjs's new "Vacuity
floors" section, which names the other two floor-carrying gates by path
— so a reader who
lands on any of the three finds the others.

⛔ The better home is AGENTS.md §"Route & surface ownership" rule 3, whose existing
tooling sentence ("a verifier that silently degrades ... is worse than no verifier") is the
weaker cousin of exactly this class. I wrote that addition and then reverted it:

✗ check-skill-line-ratchet: AGENTS.md is 1189 lines; the ratchet ceiling is 1158.
  Raising a ceiling requires a maintainer ruling quoted in the PR.

The ceiling is shrink-only and hard-coded (['AGENTS.md', 1158] in
scripts/pm/check-skill-line-ratchet.mjs); my addition cost +31 lines and I have no such
ruling. ⛔ I did not raise the ratchet, and ⛔ I did not compress unrelated prose to pay for
it. The drafted text is available on request — this is a maintainer decision, and it is the
reason this PR says "Part of" rather than closing the card.

Verification

Repo-wide pnpm lint (eslint . --no-inline-config) run in full — exit 0, no
narrowing to justify. Gate union re-run after the final commit, at 868c576877, tree
clean; every row below quotes the gate's own printed verdict, and every exit code was
captured by redirect-then-$?, never through a pipe.

gate exit its own verdict line
check:dual-build-cjs-loads 0 ✓ ... 103 published require entry point(s) across 67 package(s) load; 613 emitted CommonJS file(s) parse; 1 cross-format behaviour probe(s) agree.
check-dual-build-cjs-loads --self-test 0 ✓ ... 53 cases pass (... both stale-ledger directions including the orphan one, every vacuity floor driven to zero with its green control ...)
check:stack-collection-maps 0 ✓ ... 8 enumerations reconciled against 31 declared collections (17 waiver rows, each with a reason).
check-stack-collection-maps --self-test 0 ✓ ... 13 assertions over synthetic sources
check:agent-test-spelling 0 ✓ check-agent-test-spelling --self-test: all cases pass
check:bash32-floor 0 ✓ check-bash32-floor self-test: 153 cases pass.
check:cli-command-ids 0 ✓ check-cli-command-ids self-test: 39 cases pass ...
check:cross-package-test-inputs 0 OK: 23 package(s) read outside themselves, all declared ...
check:entry-guard 0 ✓ check-entry-guard self-test: 54 cases pass ...
check:parse-guard 0 ✓ check:parse-guard self-test: 46 cases pass ...
check:pnpm-filter-targets 0 ✓ pnpm-filter-targets --self-test: 54 assertions over 78 real workspace packages ...
check:watch-hint-literal 0 ✓ check-watch-hint-literal self-test: 29 cases pass ...
check-ci-filter-parity.mjs 0 OK: all 117 declared cross-package glob(s) (84 unique) are covered ...
check-cross-package-test-inputs.mjs 0 OK: 23 package(s) read outside themselves, all declared ...
pm/bare-root-worklist.mjs --self-test 0 OK self-test: 47 live row(s), 40 unreachable as spelled, 40 recorded verdict(s) — none stale, none missing, none contradicted.
check:pm-dispatch-gates 0 ✓ dispatch-gates self-test: 834 cases pass.
check:nul-bytes 0 ✓ check-nul-bytes --self-test: 75 assertions over a temp git repo (real scan() path)

The 12 path-derived families plus the 2 convention-triggered obligations came from
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack on the real diff
(exit 0); both gate scripts carry their test suite as --self-test rather than a vitest
file (git grep for either script name across the tree returns no test file), and both are
in the table. pnpm build was run in full first (71/71 tasks) — this gate reads dist/,
and the floor numbers are measured off that build.

Changeset: none, derived not inherited. The diff is exactly
scripts/check-dual-build-cjs-loads.mjs and scripts/check-stack-collection-maps.mjs;
nothing under packages/, examples/ or apps/, and no published surface moves. Carrying
skip-changeset.

Generated by Claude Code


Generated by Claude Code

claude added 2 commits August 29, 2026 00:50
…, and floor its vacuous pass

An exemption row whose id left the population was read by nothing: the only
lookup was `ledger[r.id]` from inside the walk over discovered rows. Measured
on 8cb96ec — a row exempting a package that does not exist left the pass
line byte-identical, exit 0, the id unmentioned.

Adds the ledger -> population pass, and four vacuity floors (entries,
packages, emitted CommonJS files, behaviour probes) that REFUSE (exit 2)
rather than report the clean tree when the sweep read nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
…CURITY_FIELDS went unread

The header said the flat extractor 'could not read it and the site was skipped
instead of failing'. Re-measured on 8cb96ec: false. This gate has refused an
empty extraction since #7032, and swapping the site's extractor back to
stringArrayItems exits 1 by name. The site was skipped because it was never a
SITE — SECURITY_FIELDS occurred 0 times in this file before #13009 (control
ARTIFACT_FIELD_TO_TYPE: 5). The hole was the hand-written SITES population, not
the extractor set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
@os-elon os-elon added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 29, 2026 — with Claude
@os-elon
os-elon marked this pull request as ready for review August 29, 2026 04:19
@os-elon
os-elon enabled auto-merge August 29, 2026 04:19
@os-elon
os-elon added this pull request to the merge queue Aug 29, 2026
Merged via the queue into main with commit 55a53ee Aug 29, 2026
34 checks passed
@os-elon
os-elon deleted the claude/issue-13014-vacuous-extractor-class branch August 29, 2026 04:38
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

Development

Successfully merging this pull request may close these issues.

2 participants