Skip to content

test(scripts): floor workspace-enumerator's folded self-test — one roster, adopted by its seven callers (#15375) - #15419

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-15375-workspace-enumerator-floor
Sep 4, 2026
Merged

test(scripts): floor workspace-enumerator's folded self-test — one roster, adopted by its seven callers (#15375)#15419
baozhoutao merged 1 commit into
mainfrom
claude/issue-15375-workspace-enumerator-floor

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #15375

scripts/workspace-enumerator.mjs exports a selfTest({ root }) with 22 static t(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 existing t() sink routes through registerCase() 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.

battery measured floor
the parse, one case per divergence the consolidation settled 18
the expansion 3
the property this module exists to keep: NO path population 1
the live half, when a caller supplies the repo root 3

SELF_TEST_BATTERY_FLOOR = 4 pins the roster's own size; UNATTRIBUTED_BATTERY catches 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-test dispatch and therefore no verdict site of its own. So workspaceEnumeratorFloorFailures() 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 a batteryFloorFailures of their own; the unprefixed name would collide in seven import lists, the same reason they all import selfTest as workspaceEnumeratorSelfTest.

⛔ The floor is not evaluated inside selfTest() before its return: 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.

caller adopts the failures as adoption line added
check-type-check-coverage.mjs failures.push(...workspaceEnumeratorSelfTest({ root: ROOT })) failures.push(...workspaceEnumeratorFloorFailures());
check-dev-prereqs.mjs failures.push(...workspaceEnumeratorSelfTest({ root: ROOT })) failures.push(...workspaceEnumeratorFloorFailures());
check-published-files.mjs failures.push(...enumeratorFailures) failures.push(...workspaceEnumeratorFloorFailures());
check-published-readme-exports.mjs failures.push(...workspaceEnumeratorSelfTest({ root: ROOT })) failures.push(...workspaceEnumeratorFloorFailures());
check-test-source-alias.mjs for (const failure of ...) expect(false, failure) for (const failure of workspaceEnumeratorFloorFailures()) expect(false, failure);
check-type-source-resolution.mjs for (const failure of ...) expect(false, failure) for (const failure of workspaceEnumeratorFloorFailures()) expect(false, failure);
pnpm-filter-targets.mjs failures.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-test and normal), captured stdout+stderr, on the installed base worktree at the same pin versus this branch: 28 of 28 cmp byte-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") and check: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 empty git diff HEAD, with trap … EXIT INT TERM. The implementation was committed before any mutation, so the restore leg's reference actually contains it.

# mutation observed
a registerCase() removed from the module's t() all 7 callers red. Each prints 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.
b early return failures; injected after selfTest()'s first case all 7 red with registered 1 case(s), below its pinned floor of 18; the other three batteries read DID NOT RUN in the same run
c roster key 'the expansion' renamed both halves: battery "the expansion" registered 3 case(s) but is not declared in SELF_TEST_BATTERIES and battery "the expansion RENAMED" DID NOT RUN — 0 cases registered, 3 pinned
d SELF_TEST_BATTERY_FLOOR raised to 9999 SELF_TEST_BATTERIES declares 4 batteries, below the pinned 9999 — a battery deleted from the roster takes its own floor with it.
e (a) still applied and the adoption line removed from check-type-check-coverage.mjs alone that caller alone exits 0 and prints its ordinary green line — ✓ 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.mjs selects on a --self-test dispatch (an argv-membership test). This module names --self-test only 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:

  • 170 rows on both trees, population identical (no row added, none removed).
  • No tally moved in any field.
  • scripts/workspace-enumerator.mjs is not a row on either tree — its absence is not a clearance, it is a file the criterion cannot reach.
  • Six of the eight edited files are census rows; all six rows are byte-identical before and after. No caller's classification moves.
  • scripts/pnpm-filter-targets.mjs is 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 intact

The 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-body red by contradicting its no-path-population marker. A new roster of string literals is exactly the shape that could breach it.

extractWatchHints run over all eight files, base versus branch: identical ×8. scripts/workspace-enumerator.mjs still contributes 0 hints. The module's own in-body guard (the battery the 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.

  • Derived family, 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 behind origin/main; re-derived after git fetch, the command list is unchanged.
  • Always-runs tail, all exit 0: check:pm-dispatch-gates (foreground under the shared verify lock, held 6m25s — ✓ dispatch-gates self-test: 1392 cases pass.), check-self-test-workflow-commands.mjs and --self-test, check:declared-population-live, check-self-test-wired.mjs and --self-test, check:ratchet-remedy-authority, check:nul-bytes, check:watch-hint-literal.
  • Every caller's own wiring, normal + --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-test runs exit 0.
  • Whole-repo pnpm lint (eslint . --no-inline-config): exit 0, no findings.
  • Control-byte self-scan over all eight edited files: clean.

skip-changeset: this PR publishes nothing from any package — it edits gate scripts and one shared script module under scripts/ only.

🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

… 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
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 4, 2026
@github-actions github-actions Bot added the size/m label Sep 4, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 4, 2026 15:09
@baozhoutao
baozhoutao enabled auto-merge September 4, 2026 15:09
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit a256cd4 Sep 4, 2026
36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-15375-workspace-enumerator-floor branch September 4, 2026 15:54
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