From 2dfb401c13fa599925a38c34720fe900fe9be825 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 11:09:31 +0000 Subject: [PATCH] build(rest): give the package a test-layer tsc program and ledger its 37 errors `packages/rest/tsconfig.json` excluded `**/*.test.ts` and `typecheck` was `tsc --noEmit` against that config, so no tsc program compiled any of the package's 149 test files and both `@ts-expect-error` directives in that layer were phantom checks. Adopts the #5286 sibling route: `tsconfig.test.json` with vitest-matching module semantics and untouched strictness, named by the `typecheck` script through the shared `check-test-typecheck.mts` gate. No test file is edited; the 37 errors the program finds (13 files) are recorded per file in `test-typecheck-debt.json`, EXACT and shrink-only. Two mechanical consequences, both required by gates rather than chosen: `@objectstack/rest` graduates out of TEST_DEBT (155 raw, re-measured 37), and its `check:type-source-resolution` registry entry gains the six workspace deps the new program reaches, with the program-set numbers stated in place. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd --- packages/rest/package.json | 5 +- packages/rest/test-typecheck-debt.json | 18 ++++++ packages/rest/tsconfig.test.json | 77 +++++++++++++++++++++++ pnpm-lock.yaml | 3 + scripts/check-type-check-coverage.mjs | 78 ++++++------------------ scripts/check-type-source-resolution.mjs | 30 ++++++++- 6 files changed, 150 insertions(+), 61 deletions(-) create mode 100644 packages/rest/test-typecheck-debt.json create mode 100644 packages/rest/tsconfig.test.json diff --git a/packages/rest/package.json b/packages/rest/package.json index 0a073f510b..72f6eaee2f 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -17,7 +17,9 @@ "build": "tsup --config ../../tsup.config.ts && node ../../scripts/check-dts-emitted.mjs", "dev": "tsc -w", "test": "vitest run", - "typecheck": "tsc --noEmit" + "check:test-typecheck": "tsx ../../scripts/check-test-typecheck.mts --self-test && tsx ../../scripts/check-test-typecheck.mts --package packages/rest --project tsconfig.test.json", + "gen:test-typecheck-debt": "tsx ../../scripts/check-test-typecheck.mts --update --package packages/rest --project tsconfig.test.json", + "typecheck": "tsc --noEmit && pnpm check:test-typecheck" }, "dependencies": { "@objectstack/core": "workspace:*", @@ -40,6 +42,7 @@ "@objectstack/service-analytics": "workspace:*", "@objectstack/service-datasource": "workspace:*", "@types/node": "^26.2.0", + "tsx": "^4.23.12", "typescript": "^6.0.3", "vitest": "^4.1.10" }, diff --git a/packages/rest/test-typecheck-debt.json b/packages/rest/test-typecheck-debt.json new file mode 100644 index 0000000000..ee248d0c1a --- /dev/null +++ b/packages/rest/test-typecheck-debt.json @@ -0,0 +1,18 @@ +{ + "_comment": "Per-file tsc error debt of the @objectstack/rest TEST layer (#5286). `tsconfig.test.json` compiles `src/**/*.test.ts` — which `tsconfig.json` excludes and therefore no gate ever read — and every file below still carries errors from before that gate existed, almost all of them fixture literals annotated with a schema OUTPUT type (`z.infer`) while holding an authored INPUT literal. EXACT ratchet, judged by re-running tsc: a file that gains errors is red, a file that loses them is red until its number is re-recorded, a file that reaches zero is red until its entry is deleted, and a file NOT listed here may have no errors at all. Regenerate with: pnpm --filter @objectstack/rest gen:test-typecheck-debt", + "entries": { + "src/analytics-read-scope-refusal-envelope.test.ts": 1, + "src/export-integration.test.ts": 17, + "src/import-dryrun-parity.test.ts": 1, + "src/import-integration.test.ts": 3, + "src/import-job-integration.test.ts": 2, + "src/meta-public-book-grant.test.ts": 1, + "src/rest-batch-size-cap.test.ts": 1, + "src/rest-expected-error-logging.test.ts": 1, + "src/rest-meta-outage-vs-miss.test.ts": 1, + "src/rest-meta-save-receipt-envelope.test.ts": 3, + "src/rest-unclassified-fault-status.test.ts": 1, + "src/rest-write-response-formula.test.ts": 1, + "src/rest.test.ts": 4 + } +} diff --git a/packages/rest/tsconfig.test.json b/packages/rest/tsconfig.test.json new file mode 100644 index 0000000000..2a7c235e13 --- /dev/null +++ b/packages/rest/tsconfig.test.json @@ -0,0 +1,77 @@ +// The TEST-layer type-check program (#12542, adopting the mechanism #5286 set +// for `packages/spec` and #5449 generalised). `tsconfig.json` above stays as it +// is: it is the BUILD config, and its `**/*.test.ts` / `**/*.spec.ts` exclusion +// has a reason — ci.yml gates that no test file reaches the published artifact. +// This sibling puts the excluded layer back in front of tsc, and +// `package.json`'s `typecheck` script NAMES it (via `check:test-typecheck +// --project`), because a config no script invokes is exactly the phantom this +// whole change is about. +// +// BEFORE THIS FILE, NO tsc PROGRAM COMPILED A SINGLE TEST FILE HERE. All 149 of +// them were named by the build config's `exclude`, and `typecheck` was +// `tsc --noEmit` against that very config — so `pnpm --filter @objectstack/rest +// typecheck` exiting 0 was a true sentence carrying no information about any +// test file in the package, and both `@ts-expect-error` directives in this +// layer (`src/rest.test.ts`, `src/rest-api-plugin-slot-lookups.test.ts`) were +// phantom checks that evaluated never. Under this program neither reports +// TS2578, so both are live and each is suppressing a real error. +// +// What differs from the build config, and what deliberately does NOT: +// - module semantics ONLY, plus `lib`. The tests are written and executed as +// ESM by vitest (esbuild/vite), and this package IS `"type": "module"`, so +// the build config's NodeNext compiles them as ESM too — and then demands +// explicit `.js` extensions on relative imports, which vitest does not. +// That one mismatch was 72 x TS2835 and, above each of them, the 49 x +// TS7006 they caused: an import that does not resolve makes every symbol it +// names `any`. `lib` inherits as ES2020 from the root config, which was 16 x +// TS2550 — all sixteen the same `Array.prototype.at` message. Those 137 +// diagnostics were about the CHECK, never about the code. Matching vitest is +// fidelity, not laxity. No `DOM` in `lib`, unlike `packages/client`: nothing +// in this layer touches a browser global. +// - ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`, +// `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch` +// are inherited from the root config, and `types: ["node"]` from +// `tsconfig.json`. Nothing here may loosen a type rule; if a test does not +// compile, that is the finding. +// +// ⚠️ WHAT THIS PROGRAM INHERITS FROM `tsconfig.json`, both of them load-bearing +// and neither of them re-declared here (read that file's #9960 comment first): +// - `rootDir: ".."`. Already widened to `packages/` there, as a CONSEQUENCE +// of the `paths` rule below rather than a preference. So the TS6059 pile +// that `packages/client`'s sibling config had to widen `rootDir` to clear +// does not arise here — it was paid for already, and this file changes +// nothing about it. +// - `paths: { "@objectstack/metadata-protocol": [".../src/index.ts"] }`. A +// child that declared its own `paths` would REPLACE this map rather than +// merge into it, silently sending that specifier back to `dist/`. This file +// declares no `paths` at all, so the rule stands and 22 of the producer's +// source files are in this program, exactly as they are in the build one. +// +// MEASURED at 5fbd58e0d, workspace closure built first: `--listFiles` puts 149 +// `src/**/*.test.ts` in the program (489 files total), and the layer reports 37 +// errors across 13 files — TS2554 x14 (wrong arity, all fourteen 'Expected 2-5 +// arguments, but got 1'), TS18048 x13 (all in `src/export-integration.test.ts`), +// TS2345 x5, TS7006 x4, TS6133 x1. Every one of the 37 is pre-existing: this +// change edits no test file, and each error would have been reported on +// `origin/main` had this program always existed. They are ledgered per file in +// `test-typecheck-debt.json` beside this config, EXACT and shrink-only — a file +// that gains an error is red, one that loses one is red until re-recorded, one +// that reaches zero is red until its entry is deleted, and a file NOT listed +// there may have no errors at all. +// +// ⚠️ `src/rest.test.ts` (4 of the 37) is also held by PR #12421, and the two +// interact BY DESIGN. After that PR merges: an error it adds to that file reds +// the ledger on ITS run, and an error it removes reds the entry as stale on the +// next run. Both are the pin working, and both land on the change that caused +// them — but only if the next reader knows the coupling is there. +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "module": "esnext", + "moduleResolution": "bundler", + "lib": ["ES2022"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0273e4d8c8..4a4021db00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2055,6 +2055,9 @@ importers: '@types/node': specifier: ^26.2.0 version: 26.2.0 + tsx: + specifier: ^4.23.12 + version: 4.23.12 typescript: specifier: ^6.0.3 version: 6.0.3 diff --git a/scripts/check-type-check-coverage.mjs b/scripts/check-type-check-coverage.mjs index 4491977567..d7dbefc3a8 100644 --- a/scripts/check-type-check-coverage.mjs +++ b/scripts/check-type-check-coverage.mjs @@ -746,6 +746,26 @@ const EXEMPT = { // interchangeable only where the excluded tests import nothing the src layer // does not, and that is a property to MEASURE per package, never to assume. // +// `@objectstack/rest` GRADUATED from this ledger (#12542; entry: 155 raw, +// re-measured 37 raw across 13 files under the sibling program). It is worth a +// line because the gap between those two numbers is the whole argument for +// fixing the CONFIG before reading the residue, and this entry's own note had +// predicted it: 121 of the 155 were TS2835 plus the implicit-any pile it +// causes, and TS2550 x16 was one `Array.prototype.at` message against a `lib` +// older than es2022. Under `tsconfig.test.json`'s vitest-matching module +// semantics both classes go to ZERO -- TS2835 x72 -> 0, TS2550 x16 -> 0, +// TS7006 x49 -> 4 -- because this package is `"type": "module"`, so NodeNext +// was compiling extensionless relative imports as unresolvable ESM and every +// symbol they named was `any`. What is LEFT is a different shape from what the +// old tally described, and it grew in one place while collapsing in four: +// TS2554 x14 (unchanged to the unit), TS18048 x13 (a class the 155 never +// contained AT ALL -- 'possibly undefined' reads that only become visible once +// the imports above them resolve to real types), TS2345 x5, TS7006 x4, +// TS6133 x1 = 37. That is the #8612 lesson this ledger already carries, +// measured a second time: collapsing a cascade EXPOSES errors as well as +// removing them, and a note sized on the TS2835 line alone would have read as +// "155 minus 121 = 34" and been wrong in both directions. +// // SINCE MEASURED, across this whole ledger rather than on that one package // (#11491, at e47d5ef61, by dropping each entry's `"**/*.test.ts"` exclusion // and reading `check:type-source-resolution`): 14 of the 18 entries that HAVE @@ -872,64 +892,6 @@ const TEST_DEBT = { + 'RECORDED EXACTLY, no bootstrap margin: this layer has never been gated, so the first new error in ' + 'it should go red rather than be absorbed.', }, - '@objectstack/rest': { - errors: 155, - note: 'RE-TALLIED from tsc at the 155 below (#10821), measured at 951b025e4 with the workspace closure ' - + 'built by the same command lint.yml runs before this gate, so the composition, the per-file split ' - + 'and the total are ONE measurement rather than a rescale: TS2835 x72 (NodeNext extensions), ' - + 'TS7006 x49, TS2550 x16 (`Array.prototype.at` against a `lib` older than es2022 -- all 16 are that ' - + 'one message), TS2554 x14 (wrong arity, all 14 \'Expected 2-5 arguments, but got 1\'), plus 4 ' - + 'singletons (TS2769 x2, TS2345, TS6133). 72+49+16+14+4 = 155, which is the recorded number ' - + 'EXACTLY -- this entry carries no remainder clause because it no longer needs one, and RECORDED ' - + 'still equals the measurement with no margin. ' - + 'WHAT THIS REPLACES, and why none of it was rescaled: the old tally read \'TS2835 x67, TS7006 x57, ' - + 'TS2554 x13, TS2550 x10 (composition as counted at 153; not re-tallied by class at the 155 ' - + 'below)\'. It summed to 147 with no clause for the remaining 6, so a reader could not tell an ' - + 'ABRIDGED tally from a SHORT one -- both readings were open and neither was checkable from the ' - + 'ledger. Both halves are settled here, and only one of them was ever answerable: (a) the 6 are NOT ' - + 'attributed and never will be. The per-class breakdown at e8db1a230 was not retained, so they are ' - + 'retired WITH the tally that carried them rather than invented, on this ledger\'s own rule that a ' - + 'made-up attribution is worse than an admitted gap. (b) 153-vs-155 was never one measurement ' - + 'disagreeing with itself: 153 was measured at e8db1a230, RECORDED was later lowered onto a ' - + 'DIFFERENT measurement (155 at 55da611, #6939), and the composition was never re-taken across that ' - + 'move -- which is precisely what the \'not re-tallied by class\' clause was saying out loud, and ' - + 'why it was honest rather than sloppy. Rescaling the old numbers onto 155 would have been wrong in ' - + 'any case, and the re-tally shows it: the shape moved in BOTH directions -- TS2835 67 -> 72, ' - + 'TS2550 10 -> 16 and TS2554 13 -> 14 rose while TS7006 57 -> 49 FELL -- and two classes the old ' - + 'tally never named (TS2769, TS2345) are in the pile now. ' - + 'Graduated from DEBT in #6905 -- this TEST_DEBT ' - + 'entry is now the sole gate on the package\'s test layer (src moved to `turbo run typecheck`). ' - + 'Measured ' - + '105 -> 136 (5ab08428) -> 143 (77adf29, hours later the same day) -> 153 (e8db1a230) -> 155 ' - + '(55da611) -> still 155 (951b025e4, the re-tally above). Read the ' - + 'top-of-ledger NodeNext note before sizing this one: TS2835 and the implicit-any pile it causes ' - + 'are 121 of the 155, and ' - + 'they are 72 IMPORT-LINE repairs, not 121: the 72 TS2835 sit on 72 distinct lines across 64 files ' - + 'but name only 11 distinct targets, and 49 of the 72 are the single `./rest-server` import. Every ' - + 'one of the 22 files carrying a TS7006 also carries a TS2835 -- no exceptions -- so there is no ' - + 'implicit-any in this layer without a broken import above it, and 31 of the 49 are one parameter ' - + 'name (`r`). Budget for the cli lesson (#8612) that collapsing a cascade can EXPOSE errors as well ' - + 'as remove them. Concentrated at the top and flat after: src/rest.test.ts x38 (TS7006 x21, ' - + 'TS2550 x11, TS2835 x4, TS2345 x1, TS6133 x1) and no other file above x5. ' - + 'Of the +10 that then bumped 153 to a bootstrap-margin RECORDED 163, ' - + '8 are attributable to three test files that ' - + 'window added -- rest-meta-save-receipt-envelope.test.ts x4 (#5265 / PR #5926), ' - + 'meta-item-envelope.test.ts x2 (#5563 / PR #5895), ' - + 'analytics-dataset-unlisted-refusal-envelope.test.ts x2; the remaining 2 landed in files that ' - + 'already existed and are NOT attributed further, because the pre-merge per-file counts were not ' - + 'retained and a made-up attribution is worse than an admitted gap. This is also the ' - + 'fastest-moving entry in either ledger, and it is ' - + 'the one that proved the gate works: #5278\'s own PR went red in CI on it, because a `pull_request` ' - + 'run builds the branch MERGED INTO main and three rest-touching PRs had landed since the sweep. A ' - + 'ledger number is always a number about a moment. RECORDED 163 stood as that bootstrap margin (+10 ' - + 'over 153 measured at e8db1a230 and re-confirmed at 153 an hour later at 77c7c884b) until #6939 ' - + '(a surplus finding filed right after the #6905 DEBT graduation) re-measured tsc at 155 -- an 8-error ' - + 'surplus the margin had been silently absorbing, and #7038 caught the note\'s "Also in DEBT." sentence ' - + 'going stale in the same graduation. Lowered 163 -> 155 at 55da611 (#6939, #7038): RECORDED now ' - + 'equals the exact measurement, no margin, so the next new error here goes red immediately -- a ' - + 'bootstrap margin can be re-established deliberately later if that slack is wanted again ' - + '(#5278 option A).', - }, '@objectstack/plugin-auth': { errors: 97, note: 'RE-TALLIED from tsc at the 97 below (#10615), measured at cea8c7d867 with the workspace closure ' diff --git a/scripts/check-type-source-resolution.mjs b/scripts/check-type-source-resolution.mjs index 3593eb75b1..9fdfe64cbd 100644 --- a/scripts/check-type-source-resolution.mjs +++ b/scripts/check-type-source-resolution.mjs @@ -319,9 +319,35 @@ const KNOWN_DIST_RESOLVED_TYPE_IMPORTS = { '@objectstack/core', '@objectstack/formula', '@objectstack/metadata-core', '@objectstack/objectql', '@objectstack/platform-objects', '@objectstack/spec', '@objectstack/types', ], + // #12542: `packages/rest` had NO tsc program compiling any of its 149 test + // files, and its new `tsconfig.test.json` (the #5286 sibling route) is the + // first one that does. The six deps after `@objectstack/core` here arrive + // from that program, exactly as `@objectstack/client`'s and + // `@objectstack/trigger-record-change`'s test-program deps do above. + // + // ⚠️ This is a program-set widening and its numbers are stated, per this + // registry's own rule: before, at 5fbd58e0d, `--list` reported 93 programs / + // 77 packages, 54 entries, 233 pairs; after, 94 programs / 77 packages, 54 + // entries, 239 pairs. +1 program, +0 entries, +6 pairs, all six in this one + // package and all six reached only through `tsconfig.test.json`. + // + // Why the entry and not `paths` rules, which is what this gate's failure text + // asks for: MEASURED both ways on the same checkout. `paths` redirecting + // these six to source puts their `src` trees in the program and takes the + // test layer from 37 errors to 42 — the +5 being TS6133 in + // `../plugins/plugin-hono-server/src/{hono-plugin,current-user-endpoints}.ts` + // and `../drivers/driver-sql/src/sql-driver.ts`, i.e. OTHER packages' source + // billed to `packages/rest/test-typecheck-debt.json`, where they would then + // red on those packages' PRs. It would also make the type program diverge + // from the runtime one: `packages/rest/vitest.config.ts` aliases exactly two + // of the six (`plugin-hono-server`, `service-datasource`) to source and + // resolves the other four through `dist/`, so blanket `paths` here is not + // fidelity to vitest either. '@objectstack/rest': [ - '@objectstack/core', '@objectstack/metadata-core', '@objectstack/objectql', - '@objectstack/observability', '@objectstack/platform-objects', '@objectstack/service-package', + '@objectstack/core', '@objectstack/driver-sql', '@objectstack/metadata', + '@objectstack/metadata-core', '@objectstack/objectql', '@objectstack/observability', + '@objectstack/platform-objects', '@objectstack/plugin-hono-server', '@objectstack/plugin-security', + '@objectstack/service-analytics', '@objectstack/service-datasource', '@objectstack/service-package', '@objectstack/spec', '@objectstack/types', ], '@objectstack/runtime': [