Uh oh!
There was an error while loading. Please reload this page.
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
Conversation
…, 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
marked this pull request as ready for review
August 29, 2026 04:19
os-elon
enabled auto-merge
August 29, 2026 04:19
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 29, 2026
Merged
This was referenced Aug 29, 2026
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 freeto 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.
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
check-stack-collection-maps) passes vacuously on an empty extractioncheck-dual-build-cjs-loads) never revisits a ledger key that left the population1. 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 remedyalready exists in that gate, and predates the incident:
The counterfactual, run for real — swap the
SECURITY_FIELDSextractor back to the flatone and see what the gate does:
⇒ 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_FIELDSwas never a SITE. Probed on the gate as itstood before #13009 —
SECURITY_FIELDS0 occurrences, positive controlARTIFACT_FIELD_TO_TYPE5 — so no extractor ever ran on it and there was nothing tocome back empty. The hole was the hand-written
SITESpopulation, which is answerable tonothing. 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 discoveredrows. 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 packagethat does not exist:
Exit 0, pass line byte-identical to the clean run, the id unmentioned. Added:
orphanLedgerRows), a separate pass overObject.keys(ledger)precisely because the orphan direction is unreachable from the rowwalk. The same file already had this right one invariant over —
runBehaviourProbesrefuses a probe naming an entry point that no longer exists — so the ledger was the odd
one out, not a new idea;
behaviour probes run), each measured on
8cb96ec41band held with margin. Below any ofthem the gate REFUSES with
exit 2, a code distinct from findings (1) andprerequisite (3), so no
if cmdreading can turn "read nothing" into a pass;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 buildstep 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.
LEG1_EXIT=1, names@objectstack/no-such-package#./ghostand "exempting nothing"LEG3_EXIT=2, "measured only 0 publishedrequireentry 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 TERMrestoring absolute paths resolved fromgit rev-parse --show-toplevel.Plus, in
--self-test(53 cases, up from 43): each floor driven to zero individually; themeasured 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:
check-keyed-text-bounds.mjs(five),
check-undeclared-dep-imports.mjs(three), and now this gate (four).population. Everyone writes population→ledger; ledger→population is the one that goes
missing.
check-stack-collection-mapsalready had it for waivers (staleWaivers);check-dual-build-cjs-loadshad 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 "Vacuityfloors" 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 existingtooling 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:
The ceiling is shrink-only and hard-coded (
['AGENTS.md', 1158]inscripts/pm/check-skill-line-ratchet.mjs); my addition cost +31 lines and I have no suchruling. ⛔ 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, nonarrowing to justify. Gate union re-run after the final commit, at
868c576877, treeclean; every row below quotes the gate's own printed verdict, and every exit code was
captured by redirect-then-
$?, never through a pipe.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.check-dual-build-cjs-loads --self-test✓ ... 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✓ ... 8 enumerations reconciled against 31 declared collections (17 waiver rows, each with a reason).check-stack-collection-maps --self-test✓ ... 13 assertions over synthetic sourcescheck:agent-test-spelling✓ check-agent-test-spelling --self-test: all cases passcheck:bash32-floor✓ check-bash32-floor self-test: 153 cases pass.check:cli-command-ids✓ check-cli-command-ids self-test: 39 cases pass ...check:cross-package-test-inputsOK: 23 package(s) read outside themselves, all declared ...check:entry-guard✓ check-entry-guard self-test: 54 cases pass ...check:parse-guard✓ check:parse-guard self-test: 46 cases pass ...check:pnpm-filter-targets✓ pnpm-filter-targets --self-test: 54 assertions over 78 real workspace packages ...check:watch-hint-literal✓ check-watch-hint-literal self-test: 29 cases pass ...check-ci-filter-parity.mjsOK: all 117 declared cross-package glob(s) (84 unique) are covered ...check-cross-package-test-inputs.mjsOK: 23 package(s) read outside themselves, all declared ...pm/bare-root-worklist.mjs --self-testOK self-test: 47 live row(s), 40 unreachable as spelled, 40 recorded verdict(s) — none stale, none missing, none contradicted.check:pm-dispatch-gates✓ dispatch-gates self-test: 834 cases pass.check:nul-bytes✓ 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/objectstackon the real diff(exit 0); both gate scripts carry their test suite as
--self-testrather than a vitestfile (
git grepfor either script name across the tree returns no test file), and both arein the table.
pnpm buildwas run in full first (71/71 tasks) — this gate readsdist/,and the floor numbers are measured off that build.
Changeset: none, derived not inherited. The diff is exactly
scripts/check-dual-build-cjs-loads.mjsandscripts/check-stack-collection-maps.mjs;nothing under
packages/,examples/orapps/, and no published surface moves. Carryingskip-changeset.Generated by Claude Code
Generated by Claude Code