Skip to content

Commit abd63ed

Browse files
fix(check-type-check-coverage): withdraw the exclusion route from the TEST_DEBT graduation remedy (#19031)
Fixes #18953 Clause-②: no `skip-changeset` — nothing published moves, and that is measured below rather than asserted. ## What this lands The `TEST_DEBT` graduation remedy printed by `scripts/check-type-check-coverage.mjs` offered two routes. Route (b) — drop the `**/*.test.ts` entry from `tsconfig.json`'s `exclude`, or widen `include` to reach the test tree — had its availability decided per package by `check:type-source-resolution`. That gate was retired under the maintainer ruling of 2026-09-18 on #18373 (PR #18946), and what it had measured did not retire with it: RED for 14 of the 18 entries that had an exclusion to drop. The maintainer ruling on this card (comment #18953 (comment) — decision batch #159 item 4, letter ②, maintainer verbatim 「同意」), quoted as it stands: > The remedy text names (a) only; (b) is removed from the printed prescription and from any > doc that restates it. An author who wants to widen `include` to the test tree does so on > their own judgement, ⛔ not on the gate's advice. Arm ① (have this gate measure the precondition itself) and arm ③ (prose suffices) were both rejected in that same ruling. ⛔ Nothing here measures anything new, and no verdict, count or floor moves. ## The diff, file by file **`scripts/check-type-check-coverage.mjs`** - `graduationRemedy()`'s `TEST_DEBT` branch now prints route (a) alone — the #5286 sibling route, marked as the only route this gate prescribes. The route-(b) sentence, and with it the retired gate's name, the `SHRINK-ONLY` clause and the 14-of-18 warning, are gone from the printed text. - The `#11491` design note above it records the withdrawal, the ruling it came from, and that the way back in is closed from both sides. - Three ledger notes that restated route (b) as a live option are turned into records of history: the `SINCE MEASURED` note (which said the message "no longer offers the exclusion route **without its precondition**" — now: not at all), the `trigger-record-change` graduation note ("the two remedies **are** interchangeable only where…"), and the `@objectstack/verify` note, whose "ROUTE (b)" label had no definition left to point at. - The `absent`-needle failure line no longer says "which is the other ledger's remedy": that was true while every anti-content needle was a cross-ledger one, and reads false on all four failures the ablation below produces. **`packages/verify/tsconfig.test.json`** — the other carrier the ruling's "any doc that restates it" reaches. It said the exclusion edit "WAS AVAILABLE HERE AND WAS NOT TAKEN, which is worth recording because `check-type-check-coverage.mjs` tells the next reader to assume it is not". That clause is false the moment the gate says nothing about the route in either direction. The measurement it carries (green for this package at `1d67130585`, under a trap-restored mutation) is kept as a reading on one tree, explicitly not as a standing clearance, and the reason the edit was declined here — vitest's module semantics, never availability — is unchanged. ## The collision, and the floors it could not move `scripts/check-type-check-coverage.mjs:6407-6416` pinned the withdrawn message in a self-test case whose own `why` ended "these needles stay exactly as they were" — written in PR #18946, about two and a half hours before this card was dispatched, on the premise that (b) would stay and be warned about in prose. Ruling ② overturns that premise, so the case is **rewritten, not deleted**, and turned from a presence-only assertion into ANTI-content: ``` present: ['tsconfig.test.json', 'the ONLY route this gate prescribes'], absent: ['exclude', 'widen', 'check:type-source-resolution', 'SHRINK-ONLY'], ``` Four needles rather than the retired gate's name alone, because that name is only one of the spellings the route could come back under. The unrecognised-ledger case's `TEST_DEBT` needle moves the other way for the same reason: it named `check:type-source-resolution`, a string no branch can emit any more, and now names `tsconfig.test.json`, which one can. No floor moved, and each half is checkable: | floor | before | after | |---|---|---| | graduation cases in the `gradCases` table | 5 | 5 | | `SELF_TEST_BATTERIES['graduation remedy (#11491)']` | 8 | 8 | | `SELF_TEST_BATTERY_FLOOR` (roster size) | 62 | 62 | | printed self-test tally | 55 / 97 / 56 / 28 / 19 / 18 | 55 / 97 / 56 / 28 / 19 / 18 | ## Ablation — the rewritten case really fails One-shot, trap-restored, run from the committed state at `0a406118e`, hashes compared against the `HEAD` blob (`scripts/…` is run by node directly, so there is no `dist` leg): ``` anchor occurrences before: 1 ABLATION-MARKER occurrences after: 1 # the mutation reached disk anchor occurrences after: 0 HEAD_HASH=479aa36f4dea2cc7b323ca0ab630ab1c85f3ead6 MUT_HASH=af87398715edb0b5e519bd285d468d81d747e004 ABLATED_SELFTEST_EXIT=1 ✗ 4 failure(s) — message STILL contains exclude / widen / check:type-source-resolution / SHRINK-ONLY BACK_HASH=479aa36f4dea2cc7b323ca0ab630ab1c85f3ead6 # byte-identical restore RESTORE: git diff HEAD is empty ABLATION-MARKER after restore: 0 RESTORED_SELFTEST_EXIT=0 ``` Direction as predicted: handing route (b) back turns the case RED on all four anti-content needles. No ablation file is left behind. ## Verification Exit codes captured before any pipe, in-worktree, at `bbd5e225a`. - `pnpm check:type-check-coverage` — **0** before the change and **0** after; its `--self-test` — **0** before and **0** after, with the same printed tally both times. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, derived in-worktree from the merge base `64de4c576`: **59 families**, all 59 run, all exit **0**, reconciled with `--ran`: `59 derived famil(ies) accounted for — 59 run, 0 NOT-MEASURED (a DERIVED zero — all 59 recorded an exit code and none of them is 3)`. That includes `pnpm check:pm-dispatch-gates` (**0**, 1849 self-test cases, 1014.7s on a contended box, run detached and waited on with `tail --pid`), `pnpm check:type-check-debt` (**0**, 4 ledger entries re-measured, 53 raw errors, none above its recorded number), `pnpm check:ratchet-remedy-authority` (**0**) and `pnpm check:nul-bytes` (**0**). ⛔ Its own warning stands: that list is not a complete account of what CI runs here. - Four of those families first answered **exit 3** (`check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:sourcemap-no-sources-content`) — PREREQUISITE NOT MET, not a finding. A full `turbo run build --filter=!@objectstack/docs --concurrency=2` under `scripts/pm/os-verify-lock.sh` (`VERDICT command-exit 0`, 73/73 tasks) and all four re-ran at **0**. - `pnpm lint` (`eslint . --no-inline-config`, the whole repo, no narrowing) — **0**. - Control characters: `grep -naP` over both changed files finds none. ## Changeset `skip-changeset`, measured on what each package's `files[]` actually ships, with a positive control: - `scripts/check-type-check-coverage.mjs` — no owning package manifest at all; shipped by nothing. - `packages/verify/tsconfig.test.json` — owner `@objectstack/verify`, whose `files[]` is `['dist', 'README.md', 'CHANGELOG.md']`. The path matches **NONE** of them. Positive control on the same matcher: `dist/index.js` matches `dist`, so the reader has a pulse. - Second leg, on the built tree: the new text (`the ONLY route this gate prescribes`) appears in **0** files under `packages/verify/dist`; positive control `bootStack` is found in three of them. The only other edit is a comment inside a config that `tsc --noEmit` reads, which no build output can carry. ## Acceptance notes Residuals found while working here, reported for the PM to file or drop — ⛔ none of them is fixed in this PR: - **`gradCases[0]`'s `absent` needle `'drop the test exclusion'` is a phantom check, and was one before this PR.** No branch of `graduationRemedy()` has ever emitted that exact string (the withdrawn text spelled it `Drop the \`**/*.test.ts\` entry from \`exclude\``), so the DEBT case's anti-content assertion can never fail. Probe: delete the needle and the self-test stays green. Left alone deliberately — it is not this ruling's subject, and this PR neither created nor worsened it. Dedupe words: phantom absent needle · graduation remedy anti-content · drop the test exclusion literal · gradCases DEBT case · never-matching self-test needle. - **`scripts/check-type-check-coverage.mjs:939` still calls a retired gate a present tense reader** ("leaving `tsconfig.json` — the only config that gate **reads** — untouched"). Pre-existing tense drift from the #18373 retirement, one sentence above one this PR does touch; noted, not filed, and the seat that next edits that paragraph is the natural carrier. --- _Generated by [Claude Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent dbd4744 commit abd63ed

2 files changed

Lines changed: 84 additions & 49 deletions

File tree

packages/verify/tsconfig.test.json

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,28 @@
1414
// line and not a probe that sees nothing. The same probe against THIS config,
1515
// on that same tree, names 10 of 10 (981 program files -> 990).
1616
//
17-
// ⚠️ ROUTE (b) WAS AVAILABLE HERE AND WAS NOT TAKEN, which is worth recording
18-
// because `check-type-check-coverage.mjs` tells the next reader to assume it is
19-
// not: dropping `**/*.test.ts` from `tsconfig.json`'s `exclude` leaves
20-
// `pnpm check:type-source-resolution` GREEN for this package (measured under a
21-
// trap-restored mutation at 1d67130585 -- exit 0, 124 programs across 78
22-
// packages), unlike the 14 of 18 entries that gate reports as red. It was
23-
// declined on module semantics, not on availability: vitest executes these
17+
// ⚠️ THE OTHER EDIT -- dropping `**/*.test.ts` from `tsconfig.json`'s
18+
// `exclude` -- WAS AVAILABLE HERE AND WAS NOT TAKEN, and the reason is worth
19+
// recording because it is not the reason the gate's prescription now gives. It
20+
// was declined on module semantics, not on availability: vitest executes these
2421
// files through vite, which resolves extension-less relative specifiers, while
2522
// `tsconfig.json` inherits `NodeNext` from the repo root and demands `.js` on
26-
// every one of them. Route (b) would hold the test layer to a resolver that
23+
// every one of them. That edit would hold the test layer to a resolver that
2724
// never runs it -- the same config-tier noise the shared gate's header
2825
// attributes 108 of spec's 842 raw errors to. Matching vitest is fidelity, not
2926
// laxity, and it is why 28 of the 30 sibling configs override these two keys.
3027
//
28+
// ⛔ Availability was never the question here, and it is nobody's question any
29+
// more: measured under a trap-restored mutation at 1d67130585, dropping that
30+
// exclusion left `pnpm check:type-source-resolution` GREEN for this package
31+
// (exit 0, 124 programs across 78 packages), unlike the 14 of 18 entries that
32+
// gate reported as red. That gate was retired (2026-09-18, #18373), and
33+
// `check-type-check-coverage.mjs` no longer offers the exclusion edit as a
34+
// route at all -- withdrawn under the maintainer ruling of 2026-09-18 on
35+
// #18953, so the sibling config is the only route it prescribes. ⛔ Read the
36+
// green above as a reading taken on one tree, never as a standing clearance,
37+
// and ⛔ read the gate's silence as neither.
38+
//
3139
// ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`, `noUnusedParameters`,
3240
// `noImplicitReturns`, `esModuleInterop` and the rest are inherited from
3341
// `tsconfig.json` (and through it from the repo root). `rootDir: ".."` and the

scripts/check-type-check-coverage.mjs

Lines changed: 68 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -937,9 +937,12 @@ const EXEMPT = {
937937
// So it took the #5286 sibling route (`tsconfig.test.json` named by the
938938
// `typecheck` script), which puts the same 10 files in front of tsc while
939939
// leaving `tsconfig.json` -- the only config that gate reads -- untouched. The
940-
// general lesson, which is this ledger's to carry: the two remedies are
940+
// general lesson, which is this ledger's to carry: the two edits were
941941
// interchangeable only where the excluded tests import nothing the src layer
942-
// does not, and that is a property to MEASURE per package, never to assume.
942+
// does not, and that was a property to MEASURE per package, never to assume.
943+
// ⛔ Nobody has to weigh them any more -- the exclusion edit is no longer an
944+
// offered route (#18953); the sibling config is the whole prescription, and
945+
// this paragraph is the record of why, not a second option.
943946
//
944947
// `@objectstack/rest` GRADUATED from this ledger (#12542; entry: 155 raw,
945948
// re-measured 37 raw across 13 files under the sibling program). It is worth a
@@ -987,10 +990,13 @@ const EXEMPT = {
987990
// an exclusion go red the way trigger-record-change did, 4 stay green
988991
// (`objectql`, `lint`, `formula`, `verify`), and the 19th (`cli`) has no
989992
// exclusion to drop at all -- its tests are hidden by an `include` that never
990-
// reaches them. So that package was the majority case, not the exception, and
991-
// the graduation message no longer offers the exclusion route without its
992-
// precondition. Re-measure before relying on the split: it moves with every
993-
// import a test file gains.
993+
// reaches them. So that package was the majority case, not the exception --
994+
// and that is the measurement the graduation message was withdrawn on: it no
995+
// longer offers the exclusion route AT ALL (maintainer ruling of 2026-09-18 on
996+
// #18953), once the gate that decided the precondition had itself been retired
997+
// (#18373). ⛔ The 14/4 split above is kept here as a READING, never as a route
998+
// to pick from: it was taken at e47d5ef61, it moves with every import a test
999+
// file gains, and nothing measures it any more.
9941000
//
9951001
// ── #14062: three plugin entries GRADUATED, and what replaced them ───────────
9961002
//
@@ -1094,14 +1100,16 @@ const EXEMPT = {
10941100
// what WOULD happen, not about an existing dead pin, exactly as `cli`'s
10951101
// graduation recorded for its own 115 files.
10961102
//
1097-
// ⚠️ ROUTE (b) WAS AVAILABLE HERE and was still not taken. The #11491 note
1098-
// above names `verify` as one of the 4 entries whose exclusion could be dropped
1099-
// with `check:type-source-resolution` staying green, and that split was
1100-
// re-measured on 2026-09-04 under a trap-restored mutation and still holds for
1101-
// this package (exit 0; 124 programs across 78 packages). It was declined on
1102-
// module semantics: `tsconfig.json` inherits NodeNext from the repo root and
1103-
// would hold the test layer to a resolver vitest never runs it under. Onboard
1104-
// by WIRING, not by widening the build config.
1103+
// ⚠️ DROPPING THE EXCLUSION WAS AVAILABLE HERE and was still not taken. The
1104+
// #11491 note above names `verify` as one of the 4 entries whose exclusion
1105+
// could be dropped with `check:type-source-resolution` staying green, and that
1106+
// split was re-measured on 2026-09-04 under a trap-restored mutation and still
1107+
// held for this package (exit 0; 124 programs across 78 packages). It was
1108+
// declined on module semantics: `tsconfig.json` inherits NodeNext from the repo
1109+
// root and would hold the test layer to a resolver vitest never runs it under.
1110+
// Onboard by WIRING, not by widening the build config -- which is now the only
1111+
// route the graduation message prescribes at all (#18953), so this record is
1112+
// history and ⛔ not a second option anybody still has to weigh.
11051113
const TEST_DEBT = {
11061114
// ── #14710: `@objectstack/cli` GRADUATED, and it was not paid down ─────────
11071115
//
@@ -4482,11 +4490,32 @@ function ratchetRemedyCarriesAuthority(message) {
44824490
//
44834491
// So the fix is not more words. It is the branch: `m.ledger` is already on
44844492
// every measurement, and each ledger's remedy prints only where it is the
4485-
// remedy. Neither is dropped -- both are still offered, in the branch that
4486-
// owns them. Within TEST_DEBT there IS a real choice of route, so that one
4487-
// keeps both and names the PRECONDITION plus the command that decides it: a
4488-
// message the reader has to open a gate's source to act on has not fixed
4489-
// anything.
4493+
// remedy. Neither LEDGER is dropped -- each still gets the remedy that is its
4494+
// own, in the branch that owns it.
4495+
//
4496+
// ── Within TEST_DEBT the exclusion route came OUT (#18953) ──────────────────
4497+
//
4498+
// TEST_DEBT used to print a SECOND route beside the sibling config: drop the
4499+
// `**/*.test.ts` entry from `exclude` (or widen `include` to reach the test
4500+
// tree). Whether that route was available for a given package was decided by
4501+
// `check:type-source-resolution` -- and that gate was RETIRED under the
4502+
// maintainer ruling of 2026-09-18 on #18373, leaving an official route whose
4503+
// precondition nothing measured any more. What it had measured is the second
4504+
// bullet above: RED for 14 of the 18 entries that had an exclusion to drop. A
4505+
// remedy that is wrong 14 times out of 18, with nothing left to say so, is not
4506+
// a remedy, so the maintainer ruling of 2026-09-18 on #18953 (decision batch
4507+
// #159 item 4, letter ②, maintainer verbatim 「同意」) withdrew it: the printed
4508+
// prescription names (a) alone, and the docs that restated the route drop it
4509+
// with the same edit.
4510+
//
4511+
// ⛔ The other way out was REJECTED in that same ruling, so the way back in is
4512+
// closed from both sides: having THIS gate measure the precondition itself
4513+
// rebuilds half of a gate the maintainer had just retired. An author who
4514+
// widens `include` to the test tree anyway does so on their own judgement --
4515+
// ⛔ not on this gate's advice, which now says nothing about that edit in
4516+
// either direction. The self-test pins the withdrawal as ANTI-content, the way
4517+
// every other branch here is pinned, because the state a well-meaning re-merge
4518+
// returns to is the one that printed both.
44904519
//
44914520
// ⛔ This changes no verdict and no number. Graduation candidates were, and
44924521
// remain, a NOTE -- never a failure.
@@ -4511,16 +4540,9 @@ function graduationRemedy({ ledger, isRoot = false }) {
45114540
`Onboard it: put the hidden test files in front of tsc, and delete the TEST_DEBT entry in the same ` +
45124541
`PR. ⛔ Adding a \`typecheck\` script is NOT the remedy here -- this ledger is "src checks, tests ` +
45134542
`are hidden", so the package already has one.\n` +
4514-
` (a) The #5286 sibling route: add a \`tsconfig.test.json\` that reaches the ` +
4515-
`tests and NAME it in the \`typecheck\` script. Always available -- it leaves \`tsconfig.json\` alone.\n` +
4516-
` (b) Drop the \`**/*.test.ts\` entry from \`exclude\` in \`tsconfig.json\` (or widen \`include\` to ` +
4517-
`reach the test tree). ⛔ The gate that DECIDED whether this route was available, ` +
4518-
`\`check:type-source-resolution\`, was RETIRED under the maintainer ruling of 2026-09-18 on #18373 ` +
4519-
`-- so nothing measures it for you any more, and ⛔ its silence is not a clearance. What it measured ` +
4520-
`has not changed: the re-admitted tests import workspace packages this package's src program never ` +
4521-
`held, its registry was ⛔ SHRINK-ONLY so registering the new ones was never the way out, and it read ` +
4522-
`red on 14 of the 18 entries that have an exclusion to drop. Treat (b) as the worse route and prefer ` +
4523-
`(a), which leaves \`tsconfig.json\` alone; nothing in this gate's own verdict will tell you.`
4543+
` (a) The #5286 sibling route, and the ONLY route this gate prescribes: add a ` +
4544+
`\`tsconfig.test.json\` that reaches the tests and NAME it in the \`typecheck\` script. Always ` +
4545+
`available -- it leaves \`tsconfig.json\` alone.`
45244546
);
45254547
}
45264548
if (ledger === 'DEBT') {
@@ -6405,15 +6427,17 @@ function selfTest() {
64056427
+ 'that remedy is a no-op on every one of them -- the misfire #11491 was filed on.',
64066428
},
64076429
{
6408-
label: 'TEST_DEBT graduation still carries what the retired gate measured about the exclusion route',
6430+
label: 'TEST_DEBT graduation prescribes the sibling config ALONE -- the exclusion route is withdrawn',
64096431
message: testDebtGrad,
6410-
present: ['check:type-source-resolution', 'SHRINK-ONLY', 'tsconfig.test.json'],
6411-
absent: [],
6412-
why: 'the exclusion route read red on 14 of the 18 entries that have an exclusion, and this gate '
6413-
+ 'never ran it. A message the author has to read a second gate\'s SOURCE to act on is the half '
6414-
+ 'of #11491 that a correct-but-terse rewrite would leave unfixed -- and once that gate was '
6415-
+ 'retired (2026-09-18, #18373) the measurement is the ONLY thing left warning the author, so '
6416-
+ 'these needles stay exactly as they were.',
6432+
present: ['tsconfig.test.json', 'the ONLY route this gate prescribes'],
6433+
absent: ['exclude', 'widen', 'check:type-source-resolution', 'SHRINK-ONLY'],
6434+
why: 'the exclusion route read red on 14 of the 18 entries that had an exclusion, and the gate that '
6435+
+ 'decided the precondition per package was retired (2026-09-18, #18373), so nothing measured it. '
6436+
+ 'The maintainer ruling of 2026-09-18 on #18953 withdrew the route rather than leave an official '
6437+
+ 'path that is wrong 14 times out of 18: the remedy names (a) alone. These are ANTI-content '
6438+
+ 'needles on purpose -- a presence-only assertion would sit green through exactly the '
6439+
+ 're-merge that hands the route back, and the retired gate\'s NAME is only one of the spellings '
6440+
+ 'it could come back under, which is why `exclude` and `widen` are named beside it.',
64176441
},
64186442
{
64196443
label: 'the workspace root graduates through `typecheck:root`, never through `typecheck`',
@@ -6428,9 +6452,12 @@ function selfTest() {
64286452
label: 'an unrecognised ledger inherits NEITHER remedy',
64296453
message: gradNote({ ledger: 'FUTURE_DEBT' }),
64306454
present: ['FUTURE_DEBT'],
6431-
absent: [ADD_SCRIPT, 'drop the test exclusion', 'check:type-source-resolution'],
6455+
absent: [ADD_SCRIPT, 'drop the test exclusion', 'tsconfig.test.json'],
64326456
why: 'a third ledger silently receiving DEBT\'s advice is how this message was wrong for TEST_DEBT '
6433-
+ 'for its whole life. Saying less is the only safe default.',
6457+
+ 'for its whole life. Saying less is the only safe default. ⚠️ The TEST_DEBT needle here is '
6458+
+ '`tsconfig.test.json` and NOT the retired gate\'s name: that name left the message when #18953 '
6459+
+ 'withdrew the exclusion route, and an anti-content needle naming a string no branch can emit '
6460+
+ 'proves nothing about inheritance.',
64346461
},
64356462
];
64366463
for (const c of gradCases) {
@@ -6442,8 +6469,8 @@ function selfTest() {
64426469
for (const needle of c.absent) {
64436470
if (c.message.includes(needle))
64446471
failures.push(
6445-
`#11491 graduation remedy — ${c.label}: message STILL contains ${needle}, which is the other `
6446-
+ `ledger's remedy. ${c.why}`,
6472+
`#11491 graduation remedy — ${c.label}: message STILL contains ${needle}, which this case pins `
6473+
+ `as ABSENT -- another ledger's remedy, or a route this one no longer offers. ${c.why}`,
64476474
);
64486475
}
64496476
}

0 commit comments

Comments
 (0)