Skip to content

Commit 45a72b0

Browse files
os-litantclaude
andauthored
fix(verify): wire the test layer into typecheck and graduate its TEST_DEBT entry (#15623)
* fix(verify): give the three ./harness test imports their .js extension Under `moduleResolution: NodeNext` a relative specifier without its `.js` extension does not resolve, and every symbol it names becomes `any`. These three are the whole of the `@objectstack/verify` TEST_DEBT entry (TS2835 x3). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * feat(verify): wire the test layer into typecheck and graduate its TEST_DEBT entry `packages/verify/tsconfig.json` excludes `**/*.test.ts` and the package's `typecheck` was a bare `tsc --noEmit` against it, so the script was green over a layer no tsc program read. Add the sibling `tsconfig.test.json` the other 30 packages use and name it in the `typecheck` chain via `scripts/check-test-typecheck.mts`. With the three `./harness` imports fixed the ledger re-measures the entry at 0 ("TEST_DEBT records 3, and tsc now reports 0 -- graduation candidate"), so the entry is deleted here rather than re-tallied, and the residue is zero: no `test-typecheck-debt.json` is seeded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * fix(verify): drop gen:test-typecheck-debt — zero residue declares no generator `check:merge-driver` requires every manifest generator to carry a merge disposition in `scripts/regen-artifacts.mjs`, and #14062's note there refuses to invent one for a ledger that does not exist. The pairing is 1:1 across all 30 sibling configs: 16 with a `test-typecheck-debt.json` declare the generator, 14 without one declare nothing. `check:test-typecheck` is what names the project, and it stays. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent abf9101 commit 45a72b0

6 files changed

Lines changed: 131 additions & 14 deletions

File tree

packages/verify/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"build": "tsup --config ../../tsup.config.ts && node ../../scripts/check-dts-emitted.mjs",
2323
"dev": "tsc -w",
2424
"test": "vitest run",
25-
"typecheck": "tsc --noEmit"
25+
"typecheck": "tsc --noEmit && pnpm check:test-typecheck",
26+
"check:test-typecheck": "tsx ../../scripts/check-test-typecheck.mts --self-test && tsx ../../scripts/check-test-typecheck.mts --package packages/verify --project tsconfig.test.json"
2627
},
2728
"dependencies": {
2829
"@objectstack/core": "workspace:*",

packages/verify/src/harness.host-resolution.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
3131
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
3232
import { tmpdir } from 'node:os';
3333
import { join } from 'node:path';
34-
import { bootStack } from './harness';
34+
import { bootStack } from './harness.js';
3535

3636
/**
3737
* Stand-in for `@objectstack/organizations`. Mirrors the real plugin's

packages/verify/src/harness.posture-only.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
// claim untestable: green whether or not the option actually stands alone.
2121

2222
import { describe, it, expect, afterEach } from 'vitest';
23-
import { bootStack } from './harness';
23+
import { bootStack } from './harness.js';
2424

2525
const app = {
2626
manifest: {

packages/verify/src/harness.posture.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// integration test.
1414

1515
import { describe, it, expect, vi, afterEach } from 'vitest';
16-
import { bootStack } from './harness';
16+
import { bootStack } from './harness.js';
1717

1818
class FakeOrganizationsPlugin {
1919
readonly name = 'fake-organizations';

packages/verify/tsconfig.test.json

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// The TEST-layer type-check program (#15145), adopting the #5286 mechanism that
2+
// #5449 generalised and that 30 sibling configs now use. `tsconfig.json` beside
3+
// this file is unchanged: it is the BUILD config, its `exclude` of
4+
// `**/*.test.ts` stays, and this sibling puts the excluded layer in front of
5+
// tsc. `package.json`'s `typecheck` NAMES this file (via
6+
// `check:test-typecheck --project`), because a config no script invokes is
7+
// exactly the phantom this whole mechanism is about.
8+
//
9+
// BEFORE THIS FILE, NO tsc PROGRAM COMPILED A SINGLE TEST FILE HERE, and that
10+
// is measured rather than read off the config. On the tree at 1d67130585 with
11+
// the workspace closure built first, `tsc --noEmit --listFiles -p tsconfig.json`
12+
// puts 0 of this package's 10 `src/*.test.ts` files in the program while
13+
// holding all 9 of its non-test `src/**` files -- so the zero is the `exclude`
14+
// line and not a probe that sees nothing. The same probe against THIS config,
15+
// on that same tree, names 10 of 10 (981 program files -> 990).
16+
//
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
24+
// files through vite, which resolves extension-less relative specifiers, while
25+
// `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
27+
// never runs it -- the same config-tier noise the shared gate's header
28+
// attributes 108 of spec's 842 raw errors to. Matching vitest is fidelity, not
29+
// laxity, and it is why 28 of the 30 sibling configs override these two keys.
30+
//
31+
// ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`, `noUnusedParameters`,
32+
// `noImplicitReturns`, `esModuleInterop` and the rest are inherited from
33+
// `tsconfig.json` (and through it from the repo root). `rootDir: ".."` and the
34+
// `@objectstack/core` source `paths` rule are inherited too, deliberately: this
35+
// program resolves core's types from source for the same #15229 reason the
36+
// build config does. Nothing here may loosen a type rule; if a test does not
37+
// compile, that is the finding.
38+
//
39+
// MEASURED on the tree at 1d67130585, workspace closure built first (an error
40+
// count taken against an unbuilt closure is not a reading -- unresolved-import
41+
// cascades inflate it): this program reports 0 errors over 10 test files, so
42+
// there is no `test-typecheck-debt.json` beside it and every error any of those
43+
// 10 files ever gains is red on arrival. 14 of the 30 sibling configs are in
44+
// that same state. This layer also holds ZERO `@ts-expect-error` directives
45+
// (grepped with a positive control -- the same grep hits `packages/spec/src`),
46+
// so no pin was silently dead here; what the gap cost was the other half.
47+
//
48+
// ⛔ AND THEREFORE NO `gen:test-typecheck-debt` SCRIPT EITHER. That pairing is
49+
// 1:1 across all 30 sibling configs -- 16 with a ledger declare the generator,
50+
// 14 without one declare nothing -- and it is enforced rather than stylistic:
51+
// `check:merge-driver` requires every manifest generator to carry a merge
52+
// disposition in `scripts/regen-artifacts.mjs`, and #14062's note on that file
53+
// refuses to invent one for a ledger that does not exist. Measured here: adding
54+
// the script alone reds that gate ("generator(s) with NO recorded merge
55+
// disposition: gen:test-typecheck-debt [@objectstack/verify]"). If this layer
56+
// ever measures non-zero, the ledger, the generator and its regen-artifacts row
57+
// arrive together, in that one PR. The `@objectstack/verify` TEST_DEBT entry in
58+
// `scripts/check-type-check-coverage.mjs` graduates in this same PR: it recorded
59+
// TS2835 x3 for three extension-less `./harness` imports, and with those three
60+
// given their `.js` the gate re-measured the entry at 0 ("TEST_DEBT records 3,
61+
// and tsc now reports 0 -- graduation candidate").
62+
{
63+
"extends": "./tsconfig.json",
64+
"compilerOptions": {
65+
"noEmit": true,
66+
"module": "esnext",
67+
"moduleResolution": "bundler",
68+
"lib": ["ES2022"],
69+
"types": ["node"]
70+
},
71+
"include": ["src/**/*"],
72+
"exclude": ["node_modules", "dist"]
73+
}

scripts/check-type-check-coverage.mjs

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,59 @@ const EXEMPT = {
10331033
// So the shrink-only guarantee did not loosen here; it moved to a strictly
10341034
// sharper instrument, one that also reddens on a wholesale substitution of
10351035
// error IDENTITY at a constant total, which a per-package integer cannot see.
1036+
//
1037+
// ── #15145: `@objectstack/verify` GRADUATED — and this one WAS paid down ───
1038+
//
1039+
// `verify` (3) left this ledger on 2026-09-04. ⛔ Do NOT read it through the
1040+
// blocks above it: those all warn that a deleted entry left its population
1041+
// undiminished, and this one is the other case (`plugin-security` is the
1042+
// nearest precedent, and even it ledgered nothing only because its 9 residual
1043+
// errors were repaired by hand). Here the entry recorded
1044+
// TS2835 x3 for three extension-less `./harness` imports, all three were given
1045+
// their `.js`, and the re-measure then read the entry at ZERO before anything
1046+
// was wired -- this gate's own line, quoted: "@objectstack/verify: TEST_DEBT
1047+
// records 3, and tsc now reports 0 -- graduation candidate". So there is no
1048+
// `packages/verify/test-typecheck-debt.json`: at zero residue a bare
1049+
// `tsc --noEmit -p tsconfig.test.json` is the stronger gate, the call
1050+
// `plugin-security`, `metadata-core`, `metadata-fs` and `trigger-record-change`
1051+
// made before it, and every error any of the package's 10 test files ever gains
1052+
// is red on arrival with no ledger to be added to.
1053+
//
1054+
// ⚠️ MEASURED IN THAT ORDER ON PURPOSE, because the entry's own note predicted
1055+
// the outcome ("the same one-line fix graduates this entry") and a prediction is
1056+
// not a licence. Collapsing a TS2835 cascade EXPOSES errors as well as removing
1057+
// them -- the #8612 lesson this ledger carries twice above -- and the card that
1058+
// filed this had itself watched the entry drift 3 -> 5 (+2 TS7006) the moment
1059+
// two `.then` callbacks were added behind that same unresolved specifier. The
1060+
// honest move on an upward count is to RE-TALLY, not to delete. Here it went
1061+
// down, and only the measurement could say which.
1062+
//
1063+
// THE WIRING IS THE OTHER HALF, and it is what the entry's disappearance now
1064+
// rests on: `packages/verify/tsconfig.test.json` compiles `src/**/*` with
1065+
// vitest-matching module semantics and `package.json`'s `typecheck` NAMES it,
1066+
// so `hidesTests` is false. Measured with the closure built: the build config
1067+
// puts 0 of the package's 10 `src/*.test.ts` in its program (and all 9 non-test
1068+
// `src/**` files); the test config puts 10 of 10. The package's own advertised
1069+
// `typecheck` was green over a layer it had never read -- which is the defect
1070+
// the card was about, and which deleting a ledger entry alone would have left
1071+
// exactly where it was.
1072+
//
1073+
// ⚠️ The pin half reported nothing here in either direction and still does not:
1074+
// this package's test layer holds ZERO `@ts-expect-error` directives (grepped
1075+
// with a positive control -- the same grep hits `packages/spec/src`), so
1076+
// PINS_CHECKED had no subject. The card's sharpest line -- that a
1077+
// `ts-expect-error` in those files is a phantom check -- is a statement about
1078+
// what WOULD happen, not about an existing dead pin, exactly as `cli`'s
1079+
// graduation recorded for its own 115 files.
1080+
//
1081+
// ⚠️ ROUTE (b) WAS AVAILABLE HERE and was still not taken. The #11491 note
1082+
// above names `verify` as one of the 4 entries whose exclusion could be dropped
1083+
// with `check:type-source-resolution` staying green, and that split was
1084+
// re-measured on 2026-09-04 under a trap-restored mutation and still holds for
1085+
// this package (exit 0; 124 programs across 78 packages). It was declined on
1086+
// module semantics: `tsconfig.json` inherits NodeNext from the repo root and
1087+
// would hold the test layer to a resolver vitest never runs it under. Onboard
1088+
// by WIRING, not by widening the build config.
10361089
const TEST_DEBT = {
10371090
// ── #14710: `@objectstack/cli` GRADUATED, and it was not paid down ─────────
10381091
//
@@ -1154,16 +1207,6 @@ const TEST_DEBT = {
11541207
+ 'module-level `VARS` and a partial context cannot satisfy it; the same file already carries a '
11551208
+ 'hand-widened copy of that helper (`filterOf`) written for exactly that reason.',
11561209
},
1157-
'@objectstack/verify': {
1158-
errors: 3,
1159-
note: 'TS2835 x3 -- `harness.host-resolution`, `harness.posture-only` and `harness.posture` each '
1160-
+ 'import `./harness` without the `.js` extension. Re-tallied from the 8 measured at 5ab08428 '
1161-
+ '(TS2835 x4, TS7006 x4) when `derive.test.ts` gained its own extension: that ONE unresolved '
1162-
+ 'import was carrying 1 x TS2835 plus every TS7006 in the file, because a specifier that does '
1163-
+ 'not resolve under NodeNext makes every symbol it names `any` and so every callback parameter '
1164-
+ 'implicitly any. The remainder is the same NodeNext pair from the top-of-ledger note, and the '
1165-
+ 'same one-line fix graduates this entry.',
1166-
},
11671210
'@objectstack/connector-mcp': { errors: 5, note: 'TS2339 x5. Re-measured 5 at 5ab08428, exact.' },
11681211
'@objectstack/connector-openapi': { errors: 5, note: 'TS2339 x5. Re-measured 5 at 5ab08428, exact.' },
11691212
'@objectstack/http-conformance': {

0 commit comments

Comments
 (0)