Skip to content

Commit fa7292c

Browse files
claude[bot]claude
andauthored
fix(lint): wire test layer into check:test-typecheck, so its 2,700-line rule pin file is type-checked (#14173) (#14494)
Onboarded by wiring to the mechanism #14062 (PR #14420) landed on, per the triage ruling on this card (comment 5502888897): a sibling tsconfig.test.json matching vitest's real module semantics, named by `typecheck`. Measured: 6 residual TS6059 errors over 2 files (rootDir violations from imports outside this package, pre-existing, config-tier), recorded in the new test-typecheck-debt.json as a CONVERSION of the coverage gate's existing 16-error TEST_DEBT entry, which is deleted as the graduation that pairing forces. Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV Co-authored-by: Claude <noreply@anthropic.com>
1 parent 99b8faf commit fa7292c

6 files changed

Lines changed: 163 additions & 28 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
"@objectstack/lint": patch
3+
---
4+
5+
fix(lint): wire `packages/lint`'s test layer into `check:test-typecheck`, so its 2,700-line rule pin file is actually type-checked (#14173)
6+
7+
`packages/lint/tsconfig.json` excluded `**/*.test.ts` and `**/*.spec.ts`, and
8+
the package's `typecheck` script was a bare `tsc --noEmit` against that very
9+
config — so no gate anywhere read a lint test file with a type checker.
10+
`src/validate-expressions.test.ts` alone is ~2,700 lines built almost entirely
11+
out of compile-time and meta pins (the #5017 receiver scan, the
12+
`TRACKED_UNDECLARED_READS` shrink-only list, the residual-root table), and none
13+
of it was type-checked by anything: vitest transpiles through esbuild (types
14+
stripped, never resolved), so a wrong key or a signature drift in a pin's own
15+
scaffolding was caught by nobody.
16+
17+
Onboarded by *wiring* to the mechanism #14062 (PR #14420) landed on
18+
(`scripts/check-test-typecheck.mts`), per the triage ruling on this card: a
19+
sibling `tsconfig.test.json` matching vitest's real module semantics
20+
(`module: esnext`, `moduleResolution: bundler`, `lib: ["ES2022"]`; strictness
21+
and `rootDir` untouched, inherited), named by `typecheck`. Measured (workspace
22+
closure built first): 6 residual errors over 2 files, all TS6059 (imports from
23+
`examples/app-showcase`, outside this package's `rootDir` — pre-existing,
24+
config-tier, not a lint defect), recorded EXACT and shrink-only in the new
25+
`test-typecheck-debt.json`.
26+
27+
This is a CONVERSION of the coverage gate's existing `@objectstack/lint`
28+
TEST_DEBT entry (`errors: 16`), not a new debt-opening decision: the same
29+
authority that recorded the 16 now holds the residue one level finer, per file
30+
and per signature, and the coverage-gate entry is deleted as the graduation
31+
that pairing forces. No test file is edited — opening the ratchet is not the
32+
same job as paying it down.

packages/lint/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
"build": "tsup && node ../../scripts/check-dts-emitted.mjs",
3333
"dev": "tsc -w",
3434
"test": "vitest run",
35-
"typecheck": "tsc --noEmit",
35+
"typecheck": "tsc --noEmit && pnpm check:test-typecheck",
36+
"check:test-typecheck": "tsx ../../scripts/check-test-typecheck.mts --self-test && tsx ../../scripts/check-test-typecheck.mts --package packages/lint --project tsconfig.test.json",
37+
"gen:test-typecheck-debt": "tsx ../../scripts/check-test-typecheck.mts --update --package packages/lint --project tsconfig.test.json",
3638
"check:doc-formula-expressions": "node scripts/check-doc-formula-expressions.mjs --self-test && node scripts/check-doc-formula-expressions.mjs",
3739
"check:doc-security-posture": "node scripts/check-doc-security-posture.mjs --self-test && node scripts/check-doc-security-posture.mjs"
3840
},
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"_comment": "Per-file tsc error debt of the @objectstack/lint 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. THIS FIELD IS GENERATED: every regeneration rewrites it from scripts/check-test-typecheck.mts, and the EXACT ratchet below requires a regeneration on every repair — so an edit made here is gone by the next one. Anything true of THIS package goes in the sibling `_note` field, which is authored, is preserved verbatim, and is never written by the generator (#12624). This comment states NO cause for the errors, deliberately: the classes differ per package and per file, they move as the debt is paid down, and a cause written here is rewritten verbatim into every ledger by every regeneration — so it outlives its own repair and cannot be corrected in the file where it is read. Measure instead, before repairing anything: `tsc --noEmit --pretty false -p tsconfig.test.json` in the package prints the real classes with their TS codes. Each entry maps a file to its per-SIGNATURE error counts, never to a bare total (#13470): a signature is the TS code plus the diagnostic message with structural type blobs collapsed, and it carries NO line or column — so the pin survives edits that move code around, and only stops matching when the error itself becomes a different error. 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, a signature that ARRIVES or VANISHES is red even when the file total is unchanged, and a file NOT listed here may have no errors at all. Regenerate with: pnpm --filter @objectstack/lint gen:test-typecheck-debt",
3+
"_note": "STARTING LEDGER, opened by #14173 under the triage ruling of 2026-09-02 (comment 5502888897 on that issue: \"onboard by wiring to whichever of the two in-repo routes #14062 lands on\"), which carries the #5286 maintainer authority for it. This is a CONVERSION, not a new debt-opening decision: the coverage gate's own `@objectstack/lint` TEST_DEBT entry (`scripts/check-type-check-coverage.mjs`, errors: 16 — TS7006 x11, TS2835 x5) already recorded this package's untyped test layer at the per-package approximation, and the 16 is the authority this ledger converts. Measured under THIS config's module semantics (`esnext`/`bundler`): the TS7006 x11 and TS2835 x5 that made up the 16 both dissolve — the same config-tier effect `packages/spec`/`packages/rest`/`packages/plugins/**` measured, here from the `.js`-extension relative-import errors resolving cleanly under `bundler` and the downstream implicit-any callbacks resolving with them. What remains is 6 TS6059 (rootDir violation) errors over 2 files, NOT part of the original 16 and explicitly EXCLUDED from it by that entry's own historical note (\"they were never this package's debt ... the generated re-measure project reporting on its own inherited rootDir\"): both files import example-app fixtures from `examples/app-showcase`, outside this package's `rootDir`, which `tsconfig.test.json` inherits untouched. Measured 6 <= 16, so the conversion stays within the triage's authority boundary. Under BUILD semantics (NodeNext, tests included, workspace closure built first) the same two files plus three ledgered-away test files measure 22 errors total (11 TS7006 + 5 TS2835 + 6 TS6059); only the 6 TS6059 survive THIS config, confirming the other 16 were config-tier. Opening the ratchet is not paying it down; paydown follows this ledger's own shrink-only discipline, on its own cards.",
4+
"entries": {
5+
"src/validate-jsx-pages.production-witness.test.ts": {
6+
"TS6059: File '…' is not under 'rootDir' '…'. 'rootDir' is expected to contain all source files.": 3
7+
},
8+
"src/validate-translatable-sections.test.ts": {
9+
"TS6059: File '…' is not under 'rootDir' '…'. 'rootDir' is expected to contain all source files.": 3
10+
}
11+
}
12+
}

packages/lint/tsconfig.test.json

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// The TEST-layer type-check program (issue #14173 — wired per the triage ruling on
2+
// that card, comment 5502888897, 2026-09-02: "onboard by *wiring* to whichever of
3+
// the two in-repo routes #14062 lands on"). #14062 landed in PR #14420 on the
4+
// `scripts/check-test-typecheck.mts` instrument, so that is the route this file
5+
// follows. `tsconfig.json` beside this one stays exactly as it is: it is the BUILD
6+
// config. This sibling puts the test layer in front of tsc under the module
7+
// semantics vitest really executes it with, and `package.json`'s `typecheck`
8+
// script NAMES it (via `check:test-typecheck --project`), because a config no
9+
// script invokes is exactly the phantom this whole change is about.
10+
//
11+
// This is a CONVERSION, not a new debt-opening decision: the coverage gate
12+
// already carried a `@objectstack/lint` TEST_DEBT entry (`scripts/check-type-check-
13+
// coverage.mjs`, `errors: 16`) recording this package's untyped test layer at the
14+
// per-PACKAGE approximation. That entry's own authority is what this file converts
15+
// to per-file/per-signature form — exactly as PR #14420's graduation docblock
16+
// describes for the plugin family ("None of them was PAID DOWN … the same errors
17+
// are now held one level finer").
18+
//
19+
// What differs from the build config, and what deliberately does NOT:
20+
// - MODULE SEMANTICS ONLY, plus `lib`. The tests are written and executed as
21+
// ESM by vitest (esbuild/vite). Matching that is FIDELITY, not laxity: it is
22+
// the same subtraction `packages/spec`, `packages/rest` and the `packages/
23+
// plugins/**` family each made, and it removes a config-tier error pile that
24+
// says nothing about the tests.
25+
// - ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`, `noUnusedParameters`,
26+
// `noImplicitReturns`, `noFallthroughCasesInSwitch`, `rootDir`, `paths` and
27+
// `types` (this package's `types: ["node"]` included) are all INHERITED from
28+
// `tsconfig.json` (and through it the root config), and none of them is
29+
// re-declared here. ⚠️ A child that declared its own `paths` would REPLACE the
30+
// parent map rather than merge into it, silently sending a source-resolved
31+
// specifier back to `dist/` — a BUILD ARTIFACT — so this file declares none.
32+
// Nothing here may loosen a type rule; if a test does not compile, that is the
33+
// finding.
34+
// - `lib: ["ES2022"]`, for the same reason `packages/rest` and `plugin-auth`
35+
// state: the root config's `lib` is ES2020 and vitest runs on a Node that has
36+
// ES2022 builtins, so the gap is reported as TS2550 about the CHECK. No `DOM`:
37+
// nothing in this layer touches a browser global.
38+
//
39+
// MEASURED at 6aea1f559, workspace closure built first (`tsc --noEmit --pretty
40+
// false --listFiles -p tsconfig.test.json`, and the same command without
41+
// `--listFiles`):
42+
//
43+
// files in this program 627
44+
// own `src/**/*.test.ts` in it 93
45+
// errors under BUILD semantics 22
46+
// errors under THIS config 6
47+
//
48+
// The two readings DISAGREE by exactly the recorded coverage-gate debt this
49+
// change converts: under BUILD semantics (NodeNext, tests included, no other
50+
// change) the total is 11 TS7006 + 5 TS2835 + 6 TS6059 = 22. The TS7006 and
51+
// TS2835 — 16 in all, the coverage gate's whole recorded `@objectstack/lint`
52+
// TEST_DEBT entry — dissolve under `bundler` resolution: TS2835 is "relative
53+
// import missing `.js`", which `bundler` does not require, and the TS7006s are
54+
// downstream implicit-`any` parameters whose callback types resolve once the
55+
// import does. Config tier, not the tests, same as `packages/spec` measured.
56+
// The 6 TS6059 that remain are `rootDir` violations from two test files
57+
// importing example-app fixtures (`examples/app-showcase`) outside this
58+
// package's `rootDir`, which this config inherits untouched (see above) — real,
59+
// pre-existing, and explicitly NOT part of the original 16 by that entry's own
60+
// prior note ("never this package's debt ... reporting on its own inherited
61+
// rootDir").
62+
//
63+
// BEFORE THIS FILE: the build config's `exclude` NAMED `**/*.test.ts` and
64+
// `**/*.spec.ts`, so none of the test files here reached any tsc program the
65+
// `typecheck` script runs (#14173).
66+
//
67+
// The residual error(s), if any, are PRE-EXISTING — this change edits no test
68+
// file, and every one of them would have been reported on `origin/main` had this
69+
// program always existed. They are ledgered per file in `test-typecheck-debt.json`
70+
// beside this config, EXACT and shrink-only: a file that gains an error is red,
71+
// one that loses an error is red until re-recorded, one that reaches zero is red
72+
// until its entry is deleted, a signature that ARRIVES or VANISHES is red even at
73+
// an unchanged file total, and a file NOT listed there may have no errors at all.
74+
// ⛔ The ruling is explicit that this PR does not REPAIR them — it makes the
75+
// ratchet able to see them; paydown follows the ratchet's own shrink-only
76+
// discipline, on its own cards.
77+
{
78+
"extends": "./tsconfig.json",
79+
"compilerOptions": {
80+
"noEmit": true,
81+
"module": "esnext",
82+
"moduleResolution": "bundler",
83+
"lib": ["ES2022"]
84+
},
85+
"include": ["src/**/*"],
86+
"exclude": ["node_modules", "dist"]
87+
}

scripts/check-type-check-coverage.mjs

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,15 @@ const EXEMPT = {
928928
// "the repair is the same one spec took -- put the file in a tsc program", and
929929
// the entry goes when the program exists, not when the number reaches zero.
930930
//
931+
// `@objectstack/lint` (16) left this ledger under the same shape on 2026-09-02
932+
// (#14173, wired to this instrument per the triage ruling on that card): the
933+
// recorded 16 (TS7006 x11, TS2835 x5) was entirely a config-tier pile -- it
934+
// dissolves under `tsconfig.test.json`'s `bundler` resolution, the same effect
935+
// measured above -- and the 6 residual TS6059 that replace it in
936+
// `test-typecheck-debt.json` were never part of the 16 at all; this entry's own
937+
// prior note already excluded them ("never this package's debt ... reporting on
938+
// its own inherited rootDir").
939+
//
931940
// So the shrink-only guarantee did not loosen here; it moved to a strictly
932941
// sharper instrument, one that also reddens on a wholesale substitution of
933942
// error IDENTITY at a constant total, which a per-package integer cannot see.
@@ -1036,33 +1045,6 @@ const TEST_DEBT = {
10361045
+ 'ratchet. Re-measured 10 at 2bc187641, and the pristine tree at that commit reports the same 10, '
10371046
+ "so none of the -33 is this PR's doing.",
10381047
},
1039-
'@objectstack/lint': {
1040-
errors: 16,
1041-
note: 'TS7006 x11, TS2835 x5, re-tallied from tsc at the 16 below -- not the older '
1042-
+ 'composition rescaled. Per file: src/validate-semantic-roles.test.ts x5, '
1043-
+ 'src/validate-dashboard-action-refs.test.ts x4, '
1044-
+ 'src/validate-filter-tokens.test.ts x3, src/validate-capability-references.test.ts x3, '
1045-
+ 'src/validate-managed-api-methods.test.ts x1. The 5 TS2835 are one per file and all the same '
1046-
+ "shape -- the test's own relative import of the module under test, missing its `.js`. "
1047-
+ 'LOWERED 19 -> 16 in #10779, re-tallied rather than declared stale because the delta is exactly '
1048-
+ 'attributable: the 3 that left are the TS6059 this itemisation used to list, all of them in '
1049-
+ 'validate-translatable-sections.test.ts, which imports contact.object.ts, contact.view.ts and '
1050-
+ 'system/translations/index.ts from examples/app-showcase -- outside this package entirely. They '
1051-
+ 'were never this package\'s debt; they were the generated re-measure project reporting on its '
1052-
+ 'own inherited `rootDir`, and no author here could have retired them by fixing lint. That file '
1053-
+ 'held exactly those 3 and so leaves the per-file list altogether. '
1054-
+ 'Measured 26 -> 30 (5ab08428, the +4 being TS6059, a file outside rootDir, a class the pre-#5278 '
1055-
+ 'note did not list) -> 32 (e8db1a230), and RECORDED 42 was a bootstrap margin (+10 over that 32). '
1056-
+ 'THE MARGIN IS GONE, and has been since #7888 / PR #8225 lowered 42 -> 20 against a measured 20 at '
1057-
+ 'b5e09b21 -- that PR deliberately left this note describing the larger pile, because inventing a '
1058-
+ 'composition for errors that are gone is the one thing this ledger forbids, so the tally above is '
1059-
+ 'the first one taken at the size the entry actually is. Lowered 20 -> 19 at 585edf738 (#8728). '
1060-
+ 'The -1 is attributed: #8515 / PR #8610 moved the translation-section-name-missing pins onto the '
1061-
+ 'frozen src/showcase-shape.fixtures.ts snapshot and dropped the live `TaskViews` import, which is '
1062-
+ 'the TS6059 that left -- the surviving three name exactly the three example files those tests '
1063-
+ 'still import. One older claim is now false and is corrected rather than carried: '
1064-
+ 'src/validate-visibility-predicates.test.ts held 10 of the 32 and reports none today.',
1065-
},
10661048
'@objectstack/formula': {
10671049
errors: 17,
10681050
note: 'TS2591 x6 (`process`), TS2345 x3, TS2352 x3, TS1470 x2, TS2339 x2, TS2739 x1. Re-measured 17 '

scripts/regen-artifacts.mjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,26 @@ export const NOT_DRIVER_MANAGED = Object.freeze([
522522
+ 'above; same generator, same per-package ledger, same reason a merge must never recompute '
523523
+ 'it.',
524524
},
525+
// ── #14173: `packages/lint`'s starting ledger, wired to the same instrument ──
526+
//
527+
// Onboarded by wiring per the triage ruling on #14173 (comment 5502888897,
528+
// 2026-09-02: "onboard by wiring to whichever of the two in-repo routes #14062
529+
// lands on"), which landed on `check:test-typecheck`. This is the fifth row
530+
// this manifest adds for the mechanism — `reconcileGenerators`'s population is
531+
// the manifest SCRIPTS, so a package that defines a generator needs a row and
532+
// one that measures zero needs none, same as the plugin family above.
533+
{
534+
path: 'packages/lint/test-typecheck-debt.json',
535+
gen: 'gen:test-typecheck-debt',
536+
owner: '@objectstack/lint',
537+
why:
538+
'a SHRINK-ONLY ratchet — see `packages/plugins/plugin-approvals/test-typecheck-debt.json` '
539+
+ 'above; same generator, same per-package ledger, same reason a merge must never recompute '
540+
+ 'it: the half-merged tree is not the tree whose type errors this file records, so a file '
541+
+ 'that GAINED errors would enter the ledger as merge noise instead of as red. Its residue is '
542+
+ 'only 6 errors over 2 files, which makes it MORE exposed to the failure this row prevents '
543+
+ 'rather than less.',
544+
},
525545
{
526546
path: 'packages/sdui-parser/objectui-lockstep.json',
527547
gen: 'gen:sdui-lockstep',

0 commit comments

Comments
 (0)