test(scripts): pin a battery roster floor on 40 more scripts/** self-tests - #14851
Conversation
`failures.length === 0` was the only success condition in these gates' self-tests, so "every case held" and "the cases never ran" printed the same line. Transplants the PR #13487 shape validated on check-doc-authoring and carried to check-self-test-wired / check-self-test-workflow-commands in PR #13797: every section opens with `battery('<name>')`, every assertion is attributed to the battery most recently opened, and a floor evaluated before the verdict requires the OPENED set to equal the DECLARED set with each battery at or above its own case count. The roster's own size is pinned too, so deleting an entry cannot silence a floor. Registered NAMES are what is pinned, never a total: a set difference says WHICH battery stopped, a count says only that something did. No case was rewritten — the only edits inside each self-test are the roster wiring (a `battery()` opener per existing section banner, one `registerCase()` line in the assertion helper, and the floor block before the verdict). Floors are the per-battery counts measured on this tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Swfxm6gT9ESmjSS7pqfVw
…o ledger-driven batteries Two batteries register one case per row of a list that is meant to shrink: `KNOWN_NUMBER_COLLISIONS` in check-adr-anchors (a resolved collision is deleted, and a stale entry already fails) and `ALLOW` in check-single-authz-resolver (an exemption that exempts nothing is dead weight). A floor at today's count would redden every legitimate removal and train the next author to edit the floor — the one habit these floors exist to prevent. Pinned instead is the part that does not move with the list: the structural cases ran AND at least one row was audited, said in place over each entry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Swfxm6gT9ESmjSS7pqfVw
…het-remedy offer The first spelling put `ALLOW` and the words "shrink-only" inside `anchorFor`'s window in check-single-authz-resolver, which flipped that file from `excluded` to `unmarked` under check:ratchet-remedy-authority (and then MISCLASSIFIED against its CONTROL row). Measured, both findings; the gate is green again with the same fact stated without the token. The adr-anchors note is reflowed for readability only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Swfxm6gT9ESmjSS7pqfVw
scripts/check-test-typecheck.mts is the one .mts file in the batch and it is reached by the root `tsc --noEmit`, so the untyped `battery()` / `registerCase()` / `floorFailure()` insertions added 7 raw errors and drifted the frozen DEBT entry for @objectstack/spec-monorepo upward (26 -> 33, measured). Typing the wiring and widening the roster to `Readonly<Record<string, number>>` puts the count back at its recorded 26 with check:type-check-debt green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Swfxm6gT9ESmjSS7pqfVw
…lf-test-roster-floor-b1
|
PM note ( Generated by Claude Code |
Batch 1b of #13799: the PR #13487 roster-floor shape transplanted onto the `scripts/**` self-tests whose assertion sink is NOT a block-bodied helper inside the self-test body -- a concise arrow, or a module-scope function -- so batch 1 (PR #14851) could not transplant it verbatim. Per the batch-1 review ruling, the roster machinery lives at MODULE scope (SELF_TEST_BATTERIES / SELF_TEST_BATTERY_FLOOR / UNATTRIBUTED_BATTERY / battery() / registerCase() / selfTestFloorFailures()), and each file's existing assertion sink is given a minimal block body that calls registerCase() and returns the original expression unchanged. No case is rewritten, none is reordered, and no assertion changes meaning: all 18 self-tests exit with the same code and byte-identical output before and after. What is pinned is the registered NAMES, not a total: every existing section banner opens a battery, every assertion is attributed to the battery most recently opened, the floor requires the OPENED set to equal the DECLARED set with each battery at or above its own count, and the roster's own size is pinned so deleting an entry cannot silence a floor quietly. One battery is pinned at its structural invariant rather than at today's count, with the reason written over the entry: check-plugin-teardown-shape's exclusions battery runs exactly one case per DELIBERATELY_EXCLUDED row, and promoting a name onto the teardown roster is a legitimate edit that shrinks that list. Census (`node scripts/measure-self-test-floor.mjs --json`): ROSTER 3 -> 21, NONE 158 -> 140; the set of files whose class changed equals this worklist exactly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015k1DVWthZyPS7xi1Q72YpK
Batch 1b of #13799: the PR #13487 roster-floor shape transplanted onto the `scripts/**` self-tests whose assertion sink is NOT a block-bodied helper inside the self-test body -- a concise arrow, or a module-scope function -- so batch 1 (PR #14851) could not transplant it verbatim. Per the batch-1 review ruling, the roster machinery lives at MODULE scope (SELF_TEST_BATTERIES / SELF_TEST_BATTERY_FLOOR / UNATTRIBUTED_BATTERY / battery() / registerCase() / batteryFloorFailures()), and each file's existing assertion sink is given a minimal block body that calls registerCase() and returns the original expression unchanged. No case is rewritten, none is reordered, and no assertion changes meaning: all 18 self-tests exit with the same code and byte-identical output before and after. What is pinned is the registered NAMES, not a total: every existing section banner opens a battery, every assertion is attributed to the battery most recently opened, the floor requires the OPENED set to equal the DECLARED set with each battery at or above its own count, and the roster's own size is pinned so deleting an entry cannot silence a floor quietly. None of the introduced helpers is named with a self-test spelling. That is deliberate and recorded beside them: `check:pm-dispatch-gates` anchors on a top-level declaration whose NAME spells self-test, and every such name owes a row in that gate's COMPOUND_ANCHOR_LEDGER. These helpers hold no fixtures to mask and read no path literal, so the accurate name is the one that says `battery`. One battery is pinned at its structural invariant rather than at today's count, with the reason written over the entry: check-plugin-teardown-shape's exclusions battery runs exactly one case per DELIBERATELY_EXCLUDED row, and promoting a name onto the teardown roster is a legitimate edit that shrinks that list. Census (`node scripts/measure-self-test-floor.mjs --json`): ROSTER 3 -> 21, NONE 158 -> 140; the set of files whose class changed equals this worklist exactly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015k1DVWthZyPS7xi1Q72YpK
Part of #13799 (batch 1, Tier B)
failures.length === 0was the only success condition in these gates' self-tests, so "every case held" and "the cases never ran" printed the same line. This lands the PR #13487 shape — the one validated oncheck-doc-authoring.mjsand carried tocheck-self-test-wired.mjs/check-self-test-workflow-commands.mjsin PR #13797 — on 40 morescripts/**self-tests.Per file: every existing section opens with
battery('NAME'), oneregisterCase()line goes into the self-test's own assertion helper so every assertion is attributed to the battery most recently opened, and a floor evaluated before the verdict requires the OPENED set to equal the DECLARED set with each battery at or above its own case count. The roster's own size is pinned too, so deleting an entry cannot silence a floor. An assertion that fires with no battery open is filed under(no battery open), which is not a declared name and reds by the same set difference.⛔ What is pinned is registered NAMES, never a total: a set difference says WHICH battery stopped, a count says only that something did. ⛔ No case was rewritten — the only edits inside each self-test are the roster wiring.
Census, measured not recalled
node scripts/measure-self-test-floor.mjs --json, on the merge base2d40f9146and again on this branch:Three set-equality assertions, all holding (computed, not eyeballed):
ROSTERafter;NONEare exactly the declared remainder below plus the honoured exclusions — symmetric difference empty, 91 = 86 + 5.The 40 files floored
scripts/check-adr-anchors.mjsscripts/check-agent-test-spelling.mjsscripts/check-aggregator-roster.mjsscripts/check-bash32-floor.mjsscripts/check-changeset-no-major.mjsscripts/check-corpus-claim-drift.mjsscripts/check-declared-population-live.mjsscripts/check-doc-route-spelling.mjsscripts/check-docs-image-tag.mjsscripts/check-docs-nav-label.mjsscripts/check-docs-redirects.mjsscripts/check-driver-conformance.mjsscripts/check-driver-memory-census.mjsscripts/check-empty-changeset.mjsscripts/check-engine-double-contract.mjsscripts/check-nul-bytes.mjsscripts/check-position-name-fold-loaders.mjsscripts/check-prerelease-pin-watch.mjsscripts/check-published-readme-exports.mjsscripts/check-published-readme-links.mjsscripts/check-query-options-erasure-ratchet.mjsscripts/check-react-page-adapter-contract.mjsscripts/check-release-page-status.mjsscripts/check-release-section-coverage.mjsscripts/check-resume-authority-declared.mjsscripts/check-role-word.mjsscripts/check-runtime-services-index.mjsscripts/check-sdui-lockstep.mjsscripts/check-shard-attestation.mjsscripts/check-single-authz-resolver.mjsscripts/check-slot-lookup-ratchet.mjsscripts/check-test-source-alias.mjsscripts/check-test-typecheck.mtsscripts/check-verify-stand-in-erasure.mjsscripts/check-where-matcher-conformance.mjsscripts/check-workflow-status-functions.mjsscripts/objectui-changeset-digest.mjsscripts/objectui-range.mjsscripts/pr-labels.mjsscripts/sync-docs-image-tags.mjs383 batteries, 2716 pinned cases.
Two floors are NOT today's count, deliberately
Per the triage ruling: where a battery is one-case-per-row of a list that only ever loses rows, a floor at today's count reddens every legitimate shrink and trains the next author to edit the floor. Both are said in place, over the roster entry:
check-adr-anchors.mjs—Live tree: green as shipped, red under ablationruns one case perKNOWN_NUMBER_COLLISIONSrow (3 today) on top of 20 structural cases; a resolved collision is meant to be deleted and the gate already fails a stale entry. Pinned 21 (20 structural + at least one row audited), not 23.check-single-authz-resolver.mjs—Every exemption carries its reason.runs one case per exemption row (2 today) plus one structural case. Pinned 2, not 3.Deferred, and why
Tier C — a decision, not a transplant (triage 5478879048; untouched here, returns as its own batch with the reshaping decision made explicitly): the inline top-level blocks
scripts/check-regen-pending.mjs,scripts/git-merge-regen.mjs,scripts/setup-git-hooks.mjs; the multi-entry dispatchesscripts/check-platform-checklist.mjs,scripts/check-durability-degradation-log-level.mjs; and table-driven self-tests whose natural roster is the table's own rows.Exclusions honoured (⛔ not edited): the #13798 batch-2 worklist in flight on
claude/issue-13798-self-test-handshake-b2(24 files);scripts/check-i18n-coverage.mjs(PR #14799);scripts/check-i18n-bundles.mjs(reserved for #14008);scripts/pm/dispatch-gates.mjs,scripts/pm/bare-root-worklist.mjs,scripts/check-type-check-coverage.mjs(open PRs #14765 / #14800 / #14805); and every file without the #13798 handshake.Carve-out, named rather than silent: three handshake-carrying files classify
COUNTrather thanNONE, so they were never in this card's 155 —scripts/check-refd-timer-probe.mjs,scripts/docs-audit/check-audit-scope.mjs,scripts/measure-stall-guard-headroom.mjs. The census header records theCOUNThits it knew about as hand-checked false positives; these three are unaudited, and are left for a later batch rather than quietly counted as floored.Remainder of the Tier B surface — 86 files, each with the measured reason it is not in this batch. These are honest transplant blockers, not skips: the transform here is a verbatim one, and each of these needs a decision (name the block-scoped groups, or reshape the report) that belongs in a batch of its own rather than at the end of a long PR.
B1 — no assertion helper inside the self-test body. Every case is written inline, or the helper lives at module scope. A roster here cannot attribute cases without rewriting the cases, which this card forbids. (48 files)
scripts/ablation-dist-preflight.mjsscripts/check-adr-links.mjsscripts/check-agent-model-declared.mjsscripts/check-auth-mount-ledger.mjsscripts/check-cli-command-ids.mjsscripts/check-cli-test-child-env.mjsscripts/check-comment-mask-corpus.mjsscripts/check-console-intercept-disarm.mjsscripts/check-cross-package-test-inputs.mjsscripts/check-declaration-mirrors.mjsscripts/check-dispatcher-error-vocabulary.mjsscripts/check-docs-section-name.mjsscripts/check-docs-single-h1.mjsscripts/check-dual-build-cjs-loads.mjsscripts/check-durability-degradation-log-level.mjsscripts/check-entry-guard.mjsscripts/check-examples-live-imports.mjsscripts/check-init-service-contract.mjsscripts/check-kernel-hook-pairs.mjsscripts/check-live-db-isolation.mjsscripts/check-optional-error-sink-contract.mjsscripts/check-org-identifier.mjsscripts/check-override-consistency.mjsscripts/check-parse-guard.mjsscripts/check-partof-closing-keyword.mjsscripts/check-plugin-teardown-shape.mjsscripts/check-published-files.mjsscripts/check-published-list-mirrors.mjsscripts/check-route-envelope.mjsscripts/check-runner-env-posture.mjsscripts/check-sdui-manifest.mjsscripts/check-single-claim-paths.mjsscripts/check-skill-compatibility-version.mjsscripts/check-skill-frame-freshness.mjsscripts/check-skill-frame-sync.mjsscripts/check-tenant-audit-census.mjsscripts/check-watch-hint-literal.mjsscripts/check-widget-option-census.mjsscripts/check-wildcard-fallthrough.mjsscripts/import-prerequisite.mjsscripts/invoked-as.mjsscripts/measure-position-name-fold-census.mjsscripts/measure-test-shard-timings.mjsscripts/partition-test-shards.mjsscripts/publish-smoke-pack.mjsscripts/qa/qa-rollup.mjsscripts/render-release-coverage-anchor.mjsscripts/ts-parse.mjsB2 — fewer than two named sections. The groups are block-scoped with no banner, so naming them is a judgement per block rather than a transplant. (15 files)
scripts/check-console-injection.mjsscripts/check-cross-repo-closer-outcome.mjsscripts/check-docs-locale-catch-all.mjsscripts/check-dts-emitted.mjsscripts/check-error-status-conformance.mjsscripts/check-logger-receiver-detach.mjsscripts/check-merge-queue-triage-outcome.mjsscripts/check-objectql-double-limit.mjsscripts/check-quick-reference-counts.mjsscripts/check-ratchet-remedy-authority.mjsscripts/check-spec-parsed-alias.mjsscripts/check-stack-collection-maps.mjsscripts/check-startup-registry-verdict.mjsscripts/check-tenant-chokepoint.mjsscripts/check-whole-set-label-write.mjsB3 — the verdict is not reached through a body-level
if (SINK…), so there is no single point at which a floor can be evaluated before the verdict without reshaping the report. (11 files)scripts/check-adr-0087-registration.mjsscripts/check-comment-mask-adoption.mjsscripts/check-doc-frontmatter.mjsscripts/check-engine-split-ratio.mjsscripts/check-i18n-stale-fill.mjsscripts/check-required-contexts.mjsscripts/check-section-landing-index.mjsscripts/check-stall-guard-budget.mjsscripts/check-system-context-census.mjsscripts/check-turbo-task-graph.mjsscripts/check-undeclared-dep-imports.mjsB4 — the self-test entry the census names has no function body this transform can resolve (an aliased or re-exported entry). (9 files)
scripts/check-ci-filter-parity.mjsscripts/check-doc-anchors.mjsscripts/check-error-code-casing.mjsscripts/check-keyed-text-bounds.mjsscripts/check-overlay-whitelist-table.mjsscripts/check-pnpm-acquisition.mjsscripts/check-vendor-version-stamps.mjsscripts/js-comment-mask.mjsscripts/release-github-releases.mjsB5 — the only named sections sit after the verdict site. (2 files)
scripts/check-dev-prereqs.mjsscripts/sync-template-versions.mjsB3 — same as above (
failedsink). (1 files)scripts/docs-audit/affected-docs.mjsVerification
--self-testrun before and after; exit codes identical on all 40 (all 0). Floors are the per-battery counts measured by instrumenting the wiring on this tree, not guessed.git diff HEAD:check-sdui-lockstep.mjs) → exit 1;check-sdui-lockstep.mjs) → exit 1, and the run names both halves: "declares 4 batteries, below the pinned 5" and "registered 7 case(s) but is not declared in SELF_TEST_BATTERIES";check-bash32-floor.mjs) → exit 1, "battery 'the table itself' DID NOT RUN — 0 cases registered, 2 pinned".node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackon this branch, all run, exit codes captured before any pipe. All 41 exit 0 on the merged head3da3d92.pnpm lint(whole repo,eslint . --no-inline-config): exit 0.check:ratchet-remedy-authoritywent red because the first spelling of theALLOWnote put that registry's name and the words "shrink-only" insideanchorFor's window, flippingcheck-single-authz-resolver.mjsfromexcludedtounmarkedand then MISCLASSIFIED against its CONTROL row. Reworded to state the same fact without the token; green.check:type-check-debtwent red becausescripts/check-test-typecheck.mtsis reached by the roottsc --noEmitand the untyped wiring added 7 raw errors (26 → 33, drifting a frozen DEBT entry upward). Typed; back to the recorded 26.skip-changeset:scripts/**only, nothing is published from any package.Generated by Claude Code