diff --git a/packages/verify/package.json b/packages/verify/package.json index 3f560ba454..3ebe106911 100644 --- a/packages/verify/package.json +++ b/packages/verify/package.json @@ -22,7 +22,8 @@ "build": "tsup --config ../../tsup.config.ts && node ../../scripts/check-dts-emitted.mjs", "dev": "tsc -w", "test": "vitest run", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit && pnpm check:test-typecheck", + "check:test-typecheck": "tsx ../../scripts/check-test-typecheck.mts --self-test && tsx ../../scripts/check-test-typecheck.mts --package packages/verify --project tsconfig.test.json" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/verify/src/harness.host-resolution.test.ts b/packages/verify/src/harness.host-resolution.test.ts index 749d621735..c89a1c1b1d 100644 --- a/packages/verify/src/harness.host-resolution.test.ts +++ b/packages/verify/src/harness.host-resolution.test.ts @@ -31,7 +31,7 @@ import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest'; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { bootStack } from './harness'; +import { bootStack } from './harness.js'; /** * Stand-in for `@objectstack/organizations`. Mirrors the real plugin's diff --git a/packages/verify/src/harness.posture-only.test.ts b/packages/verify/src/harness.posture-only.test.ts index 0d4fbba30c..b143ece5ab 100644 --- a/packages/verify/src/harness.posture-only.test.ts +++ b/packages/verify/src/harness.posture-only.test.ts @@ -20,7 +20,7 @@ // claim untestable: green whether or not the option actually stands alone. import { describe, it, expect, afterEach } from 'vitest'; -import { bootStack } from './harness'; +import { bootStack } from './harness.js'; const app = { manifest: { diff --git a/packages/verify/src/harness.posture.test.ts b/packages/verify/src/harness.posture.test.ts index 414dc5c326..be69c2f333 100644 --- a/packages/verify/src/harness.posture.test.ts +++ b/packages/verify/src/harness.posture.test.ts @@ -13,7 +13,7 @@ // integration test. import { describe, it, expect, vi, afterEach } from 'vitest'; -import { bootStack } from './harness'; +import { bootStack } from './harness.js'; class FakeOrganizationsPlugin { readonly name = 'fake-organizations'; diff --git a/packages/verify/tsconfig.test.json b/packages/verify/tsconfig.test.json new file mode 100644 index 0000000000..25bb5b4030 --- /dev/null +++ b/packages/verify/tsconfig.test.json @@ -0,0 +1,73 @@ +// The TEST-layer type-check program (#15145), adopting the #5286 mechanism that +// #5449 generalised and that 30 sibling configs now use. `tsconfig.json` beside +// this file is unchanged: it is the BUILD config, its `exclude` of +// `**/*.test.ts` stays, and this sibling puts the excluded layer in front of +// tsc. `package.json`'s `typecheck` NAMES this file (via +// `check:test-typecheck --project`), because a config no script invokes is +// exactly the phantom this whole mechanism is about. +// +// BEFORE THIS FILE, NO tsc PROGRAM COMPILED A SINGLE TEST FILE HERE, and that +// is measured rather than read off the config. On the tree at 1d67130585 with +// the workspace closure built first, `tsc --noEmit --listFiles -p tsconfig.json` +// puts 0 of this package's 10 `src/*.test.ts` files in the program while +// holding all 9 of its non-test `src/**` files -- so the zero is the `exclude` +// line and not a probe that sees nothing. The same probe against THIS config, +// on that same tree, names 10 of 10 (981 program files -> 990). +// +// ⚠️ ROUTE (b) 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: dropping `**/*.test.ts` from `tsconfig.json`'s `exclude` leaves +// `pnpm check:type-source-resolution` GREEN for this package (measured under a +// trap-restored mutation at 1d67130585 -- exit 0, 124 programs across 78 +// packages), unlike the 14 of 18 entries that gate reports as red. It was +// declined on module semantics, not on availability: vitest executes these +// files through vite, which resolves extension-less relative specifiers, while +// `tsconfig.json` inherits `NodeNext` from the repo root and demands `.js` on +// every one of them. Route (b) would hold the test layer to a resolver that +// never runs it -- the same config-tier noise the shared gate's header +// attributes 108 of spec's 842 raw errors to. Matching vitest is fidelity, not +// laxity, and it is why 28 of the 30 sibling configs override these two keys. +// +// ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`, `noUnusedParameters`, +// `noImplicitReturns`, `esModuleInterop` and the rest are inherited from +// `tsconfig.json` (and through it from the repo root). `rootDir: ".."` and the +// `@objectstack/core` source `paths` rule are inherited too, deliberately: this +// program resolves core's types from source for the same #15229 reason the +// build config does. Nothing here may loosen a type rule; if a test does not +// compile, that is the finding. +// +// MEASURED on the tree at 1d67130585, workspace closure built first (an error +// count taken against an unbuilt closure is not a reading -- unresolved-import +// cascades inflate it): this program reports 0 errors over 10 test files, so +// there is no `test-typecheck-debt.json` beside it and every error any of those +// 10 files ever gains is red on arrival. 14 of the 30 sibling configs are in +// that same state. This layer also holds ZERO `@ts-expect-error` directives +// (grepped with a positive control -- the same grep hits `packages/spec/src`), +// so no pin was silently dead here; what the gap cost was the other half. +// +// ⛔ AND THEREFORE NO `gen:test-typecheck-debt` SCRIPT EITHER. That pairing is +// 1:1 across all 30 sibling configs -- 16 with a ledger declare the generator, +// 14 without one declare nothing -- and it is enforced rather than stylistic: +// `check:merge-driver` requires every manifest generator to carry a merge +// disposition in `scripts/regen-artifacts.mjs`, and #14062's note on that file +// refuses to invent one for a ledger that does not exist. Measured here: adding +// the script alone reds that gate ("generator(s) with NO recorded merge +// disposition: gen:test-typecheck-debt [@objectstack/verify]"). If this layer +// ever measures non-zero, the ledger, the generator and its regen-artifacts row +// arrive together, in that one PR. The `@objectstack/verify` TEST_DEBT entry in +// `scripts/check-type-check-coverage.mjs` graduates in this same PR: it recorded +// TS2835 x3 for three extension-less `./harness` imports, and with those three +// given their `.js` the gate re-measured the entry at 0 ("TEST_DEBT records 3, +// and tsc now reports 0 -- graduation candidate"). +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "module": "esnext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/scripts/check-type-check-coverage.mjs b/scripts/check-type-check-coverage.mjs index 2fc720be42..195f410dad 100644 --- a/scripts/check-type-check-coverage.mjs +++ b/scripts/check-type-check-coverage.mjs @@ -1033,6 +1033,59 @@ const EXEMPT = { // So the shrink-only guarantee did not loosen here; it moved to a strictly // sharper instrument, one that also reddens on a wholesale substitution of // error IDENTITY at a constant total, which a per-package integer cannot see. +// +// ── #15145: `@objectstack/verify` GRADUATED — and this one WAS paid down ─── +// +// `verify` (3) left this ledger on 2026-09-04. ⛔ Do NOT read it through the +// blocks above it: those all warn that a deleted entry left its population +// undiminished, and this one is the other case (`plugin-security` is the +// nearest precedent, and even it ledgered nothing only because its 9 residual +// errors were repaired by hand). Here the entry recorded +// TS2835 x3 for three extension-less `./harness` imports, all three were given +// their `.js`, and the re-measure then read the entry at ZERO before anything +// was wired -- this gate's own line, quoted: "@objectstack/verify: TEST_DEBT +// records 3, and tsc now reports 0 -- graduation candidate". So there is no +// `packages/verify/test-typecheck-debt.json`: at zero residue a bare +// `tsc --noEmit -p tsconfig.test.json` is the stronger gate, the call +// `plugin-security`, `metadata-core`, `metadata-fs` and `trigger-record-change` +// made before it, and every error any of the package's 10 test files ever gains +// is red on arrival with no ledger to be added to. +// +// ⚠️ MEASURED IN THAT ORDER ON PURPOSE, because the entry's own note predicted +// the outcome ("the same one-line fix graduates this entry") and a prediction is +// not a licence. Collapsing a TS2835 cascade EXPOSES errors as well as removing +// them -- the #8612 lesson this ledger carries twice above -- and the card that +// filed this had itself watched the entry drift 3 -> 5 (+2 TS7006) the moment +// two `.then` callbacks were added behind that same unresolved specifier. The +// honest move on an upward count is to RE-TALLY, not to delete. Here it went +// down, and only the measurement could say which. +// +// THE WIRING IS THE OTHER HALF, and it is what the entry's disappearance now +// rests on: `packages/verify/tsconfig.test.json` compiles `src/**/*` with +// vitest-matching module semantics and `package.json`'s `typecheck` NAMES it, +// so `hidesTests` is false. Measured with the closure built: the build config +// puts 0 of the package's 10 `src/*.test.ts` in its program (and all 9 non-test +// `src/**` files); the test config puts 10 of 10. The package's own advertised +// `typecheck` was green over a layer it had never read -- which is the defect +// the card was about, and which deleting a ledger entry alone would have left +// exactly where it was. +// +// ⚠️ The pin half reported nothing here in either direction and still does not: +// this package's test layer holds ZERO `@ts-expect-error` directives (grepped +// with a positive control -- the same grep hits `packages/spec/src`), so +// PINS_CHECKED had no subject. The card's sharpest line -- that a +// `ts-expect-error` in those files is a phantom check -- is a statement about +// what WOULD happen, not about an existing dead pin, exactly as `cli`'s +// graduation recorded for its own 115 files. +// +// ⚠️ ROUTE (b) WAS AVAILABLE HERE and was still not taken. The #11491 note +// above names `verify` as one of the 4 entries whose exclusion could be dropped +// with `check:type-source-resolution` staying green, and that split was +// re-measured on 2026-09-04 under a trap-restored mutation and still holds for +// this package (exit 0; 124 programs across 78 packages). It was declined on +// module semantics: `tsconfig.json` inherits NodeNext from the repo root and +// would hold the test layer to a resolver vitest never runs it under. Onboard +// by WIRING, not by widening the build config. const TEST_DEBT = { // ── #14710: `@objectstack/cli` GRADUATED, and it was not paid down ───────── // @@ -1154,16 +1207,6 @@ const TEST_DEBT = { + 'module-level `VARS` and a partial context cannot satisfy it; the same file already carries a ' + 'hand-widened copy of that helper (`filterOf`) written for exactly that reason.', }, - '@objectstack/verify': { - errors: 3, - note: 'TS2835 x3 -- `harness.host-resolution`, `harness.posture-only` and `harness.posture` each ' - + 'import `./harness` without the `.js` extension. Re-tallied from the 8 measured at 5ab08428 ' - + '(TS2835 x4, TS7006 x4) when `derive.test.ts` gained its own extension: that ONE unresolved ' - + 'import was carrying 1 x TS2835 plus every TS7006 in the file, because a specifier that does ' - + 'not resolve under NodeNext makes every symbol it names `any` and so every callback parameter ' - + 'implicitly any. The remainder is the same NodeNext pair from the top-of-ledger note, and the ' - + 'same one-line fix graduates this entry.', - }, '@objectstack/connector-mcp': { errors: 5, note: 'TS2339 x5. Re-measured 5 at 5ab08428, exact.' }, '@objectstack/connector-openapi': { errors: 5, note: 'TS2339 x5. Re-measured 5 at 5ab08428, exact.' }, '@objectstack/http-conformance': {