test(scripts): floor workspace-enumerator's folded self-test — one roster, adopted by its seven callers (#15375) - #15419
Merged
Conversation
… by its seven callers `selfTest()` carried 22 static `t(name, ok)` sites and a failure-only sink, so "every case held" and "the cases never ran" returned the same empty list -- and the seven gates that fold it in decided on that list alone. None of them could floor the module's cases, because a green run contributes zero registrations to any of their ledgers. Pin the registered NAMES in the module. The body's four named section banners are four batteries, opened by `battery()` on the banner's own line; the existing `t()` sink routes through `registerCase()` with no case rewritten and no assertion changed. Floors are MEASURED on a run -- 18 / 3 / 1 / 3 -- and the roster's own size is pinned at 4. The check is EXPORTED rather than placed: this module is deliberately not a gate, so it has no `--self-test` dispatch and no verdict site of its own. Each of the seven folding callers adopts `workspaceEnumeratorFloorFailures()` exactly where it already adopts the module's failures -- five spread it beside the existing spread, two loop it through `expect(false, failure)`. Adopting AFTER the call is what makes an early return above the cases read as a breach; evaluated before `selfTest()`'s own `return` it would be skipped by the same early return it exists to catch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
baozhoutao
marked this pull request as ready for review
September 4, 2026 15:09
baozhoutao
enabled auto-merge
September 4, 2026 15:09
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 #15375
scripts/workspace-enumerator.mjsexports aselfTest({ root })with 22 statict(name, ok)sites and a failure-only sink, and seven gates fold it into their own. Every one of them adopts its failures, and none could floor its cases: on a green run the fold-in contributes zero registrations to every caller's ledger, so the cases could stop running and all seven would keep printing their verdict lines unchanged. That is hole 1 of #13489, one level below the population #13489 measures.Landed as option 2 of the card, shaped by the ruling in comment 5541674644: the roster lives in ONE module and each caller pays one line.
The module
The body carries four named section banners, so this is the Tier B sectioned shape (one battery per banner, as PR #15327 landed) rather than the single hoisted battery of PR #15217.
battery()opens each battery on the banner's own line; the existingt()sink routes throughregisterCase()as its first statement. No case was rewritten and no assertion changed.Floors are MEASURED on a run, not guessed: pinned at 9999 first, then read off the breach lines. All four batteries breached, and the breach count equalled the roster size — every declared battery is reachable and was measured.
the parse, one case per divergence the consolidation settledthe expansionthe property this module exists to keep: NO path populationthe live half, when a caller supplies the repo rootSELF_TEST_BATTERY_FLOOR = 4pins the roster's own size;UNATTRIBUTED_BATTERYcatches an assertion filed under no open battery.Why the check is EXPORTED rather than placed. Unlike every other file in this recipe, this module is deliberately not a gate — it has no
--self-testdispatch and therefore no verdict site of its own. SoworkspaceEnumeratorFloorFailures()is exported and each caller evaluates it at its own verdict site. It is prefixed with the module's name because all seven callers already declare abatteryFloorFailuresof their own; the unprefixed name would collide in seven import lists, the same reason they all importselfTest as workspaceEnumeratorSelfTest.⛔ The floor is not evaluated inside
selfTest()before itsreturn: an early return above the cases would skip the check and the cases together — the one defect this card exists to catch. Adopted after the call, in a caller, the same early return lands as a count below the floor and reds. "Reds in seven places for one cause" is the accepted cost; one breach line names the module.The seven callers — one adoption line each
No caller's own roster, condition, message or argument changed. Each edit is its import plus one line, placed exactly where that caller already adopts the module's failures.
check-type-check-coverage.mjsfailures.push(...workspaceEnumeratorSelfTest({ root: ROOT }))failures.push(...workspaceEnumeratorFloorFailures());check-dev-prereqs.mjsfailures.push(...workspaceEnumeratorSelfTest({ root: ROOT }))failures.push(...workspaceEnumeratorFloorFailures());check-published-files.mjsfailures.push(...enumeratorFailures)failures.push(...workspaceEnumeratorFloorFailures());check-published-readme-exports.mjsfailures.push(...workspaceEnumeratorSelfTest({ root: ROOT }))failures.push(...workspaceEnumeratorFloorFailures());check-test-source-alias.mjsfor (const failure of ...) expect(false, failure)for (const failure of workspaceEnumeratorFloorFailures()) expect(false, failure);check-type-source-resolution.mjsfor (const failure of ...) expect(false, failure)for (const failure of workspaceEnumeratorFloorFailures()) expect(false, failure);pnpm-filter-targets.mjsfailures.push(...workspaceEnumeratorSelfTest({ root: root ?? HERE }))failures.push(...workspaceEnumeratorFloorFailures());8 files, +156 lines, 0 deletions.
Green lines are byte-identical
Every caller run in both modes (
--self-testand normal), captured stdout+stderr, on the installed base worktree at the same pin versus this branch: 28 of 28cmpbyte-identical, exit codes identical. The base tree was run twice first as the control — no line varies between two runs of the same tree, so there is no legitimately-varying line to exempt.Two gates read NOT MEASURED on both trees because neither is built:
check:published-readme-exports(exit 1, "Build first: ... dist/index.d.ts does not exist") andcheck:type-check-debt(exit 3,PREREQUISITE NOT MET). Their output is byte-identical base-vs-branch, so the reading is a fact about the unbuilt tree, not about this change.Ablations
Mutation proven on disk each time (blob hash vs the HEAD blob, plus grep counts of the exact text; the injection-only leg also pins the file's line growth). Restore is
git checkout HEAD -- ABSOLUTE_PATH, proven by hash equality against the HEAD blob and an emptygit diff HEAD, withtrap … EXIT INT TERM. The implementation was committed before any mutation, so the restore leg's reference actually contains it.registerCase()removed from the module'st()battery "the parse, …" DID NOT RUN — 0 cases registered, 18 pinned. Each reports exactly 5 failures, all 5 from the module — every caller's own cases still green.return failures;injected afterselfTest()'s first caseregistered 1 case(s), below its pinned floor of 18; the other three batteries read DID NOT RUN in the same run'the expansion'renamedbattery "the expansion" registered 3 case(s) but is not declared in SELF_TEST_BATTERIESandbattery "the expansion RENAMED" DID NOT RUN — 0 cases registered, 3 pinnedSELF_TEST_BATTERY_FLOORraised to 9999SELF_TEST_BATTERIES declares 4 batteries, below the pinned 9999 — a battery deleted from the roster takes its own floor with it.check-type-check-coverage.mjsalone✓ check:type-check-coverage --self-test — 48 semantic case(s) + … hold.— while the other six stay red. The adoption is load-bearing per caller.Ablation (e) is the one that matters for review: with the module's cases not running at all, a caller that has not adopted the breaches prints a fully green verdict claiming cases hold that never ran. That is precisely the pre-existing state this PR closes, reproduced on demand.
The census-visibility reading (recorded, not fixed)
measure-self-test-floor.mjsselects on a--self-testdispatch (an argv-membership test). This module names--self-testonly in prose, so it stays outside the measured population — deliberately, per its own header, which rules out giving it a CI invocation of its own.Measured before and after on this branch,
node scripts/measure-self-test-floor.mjs --json:scripts/workspace-enumerator.mjsis not a row on either tree — its absence is not a clearance, it is a file the criterion cannot reach.scripts/pnpm-filter-targets.mjsis also not a row.The instrument is untouched. Its population criterion remains the standing question on #15391 / #15371 / #14963's file; this reading is recorded here so a later instrument card can cite it.
extractWatchHints— the module's load-bearing property is intactThe module's header argues at length that a path-shaped literal anywhere in its body is inherited as a watch hint by every importer, priced at +41725 (gate, file) pairs, and that it would turn
check:release-bodyred by contradicting itsno-path-populationmarker. A new roster of string literals is exactly the shape that could breach it.extractWatchHintsrun over all eight files, base versus branch: identical ×8.scripts/workspace-enumerator.mjsstill contributes 0 hints. The module's own in-body guard (the batterythe property this module exists to keep: NO path population) also still passes on its new bytes.Gates
Run at
b3cc2cd57, the commit pushed here, with a clean working tree; the union was measured on that head. Exit codes captured by redirecting first, never read through a pipe.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no hand-built list; the tool took the 8 paths from git itself) — 29 commands, every one run, all exit 0 except the two unbuilt-tree NOT MEASURED readings above. The tool warns the branch base is behindorigin/main; re-derived aftergit fetch, the command list is unchanged.check:pm-dispatch-gates(foreground under the shared verify lock, held 6m25s —✓ dispatch-gates self-test: 1392 cases pass.),check-self-test-workflow-commands.mjsand--self-test,check:declared-population-live,check-self-test-wired.mjsand--self-test,check:ratchet-remedy-authority,check:nul-bytes,check:watch-hint-literal.--self-test:check:type-check-coverage,check:dev-prereqs,check:published-files,check:published-readme-exports,check:test-source-alias,check:type-source-resolution,check:pnpm-filter-targets. All seven--self-testruns exit 0.pnpm lint(eslint . --no-inline-config): exit 0, no findings.skip-changeset: this PR publishes nothing from any package — it edits gate scripts and one shared script module underscripts/only.🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code