build(repo): typecheck the test sources and close the last Phase 1 items - #8
Merged
Conversation
The last two Phase 1 deferred items are packet 4B step 5 and packet 5B step 5, both @ts-expect-error and type-identity assertions. Writing them exposed that they could not have meant anything: packages/ai-tooling/tsconfig.json has include: ["src/**/*.ts"] and Vitest transpiles without checking types, so no .spec.ts file had ever been seen by a compiler. An @ts-expect-error would have suppressed nothing and failed nothing. The plan assumed otherwise -- its expected result is "every @ts-expect-error is exercised", which requires a compiler that sees them. Two shapes were rejected before the third was chosen. Extending the workspace tsconfig fails because rootDir: src with composite and outDir: dist would emit the tests into dist, and pack:check asserts the tarball contents exactly. A tsconfig.test.json beside the tests fails because check-stage1-artifacts.mjs reports any file under its owned roots -- packages/ai-tooling and configs/ai -- that no phase manifest declares as undeclared-entry, and those manifests are committed records of what each phase delivered, not lists to be reopened. So the guard lives at scripts/check-test-types.mjs, beside the four that already exist, outside the scanner's roots. It derives its compiler options from each workspace's own tsconfig.json rather than restating them, minus the emit-shaped ones, so the two cannot drift apart. allowJs is the single added option and it is load-bearing: two specs import the repository's own scripts/*.mjs guards directly, and without it every such import is an implicit any and the assertions written against them check nothing. checkJs stays off. The guard is deliberately not part of pnpm check, which Stage 1 phase gates invoke literally and which must not acquire new failure modes; it is its own script and its own CI step, like check:structure and check:supply-chain. Two TypeScript 7 behaviours shape the invocation, both measured: TS5112 refuses command-line file arguments while a tsconfig.json is present, so --ignoreConfig is required, and TS6053 shows that file arguments are not glob-expanded, so the file list is enumerated through git ls-files. Turning it on surfaced 6 real type errors in four existing spec files, none of them introduced here: four noUncheckedIndexedAccess violations indexing a regex capture and a Uint8Array, one unchecked sources[0], and one exactOptionalPropertyTypes violation passing candidate: undefined explicitly where omitting the property was meant. All six are fixed by narrowing rather than by loosening a compiler option. The guard is proven in both directions rather than assumed. A deliberate const x: number = "string" in a spec makes it exit 1 with the exact diagnostic, and removing it restores exit 0. Removing the @ts-expect-error markers makes all five suppressed errors surface, which is what shows they are real: TS2558 that parseStrictJson<ConfigV1> is not expressible, TS2741 that StrictJsonDocument cannot be built from a literal, TS2739 that parser output is not a domain type, TS2739 that validate('lock', ...) is not ConfigV1, and TS2345 that the generic follows the name argument rather than the caller. That is packet 4B step 5's three requirements and packet 5B step 5's three.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What lands and why
The last two Phase 1 deferred items — packet 4B step 5 and packet 5B step 5 — are
@ts-expect-errorand type-identity assertions. Writing them exposed that they could not have meant anything.
packages/ai-tooling/tsconfig.jsonhasinclude: ["src/**/*.ts"], and Vitest transpiles withoutchecking types. No
.spec.tsfile had ever been seen by a compiler. An@ts-expect-errorwouldhave suppressed nothing and failed nothing. The plan assumed otherwise; its expected result is
"every
@ts-expect-erroris exercised", which requires a compiler that sees them.Why the guard lives at the repository root
Two shapes were rejected first:
tsconfig.json—rootDir: srcwithcompositeandoutDir: distwould emit the tests into
dist, andpack:checkasserts the published tarball's contentsexactly.
tsconfig.test.jsonbeside the tests —check-stage1-artifacts.mjsreports any file underits owned roots (
packages/ai-tooling,configs/ai) that no phase manifest declares asundeclared-entry, and those manifests are committed records of what each phase delivered, notlists to be reopened.
So it is
scripts/check-test-types.mjs, beside the four guards that already exist, outside thescanner's roots.
It derives its compiler options from each workspace's own
tsconfig.jsonrather than restating them,minus the emit-shaped ones, so the two cannot drift apart.
allowJsis the single added option andit is load-bearing: two specs import the repository's own
scripts/*.mjsguards directly, andwithout it every such import is an implicit
any(TS7016) and the assertions written against themcheck nothing.
checkJsstays off.It is deliberately not part of
pnpm check, which Stage 1 phase gates invoke literally and whichmust not acquire new failure modes. It runs as its own script and its own CI step, exactly like
check:structureandcheck:supply-chain— inside the existingNode 24 (pnpm)job, which isalready a required check on both platforms, so no branch-protection change is needed.
Two TypeScript 7 behaviours shape the invocation, both measured rather than assumed:
TS5112— command-line file arguments refused while atsconfig.jsonis present--ignoreConfigis requiredTS6053— file arguments are not glob-expandedgit ls-filesSix real type errors, none introduced here
Turning the guard on surfaced six errors in four existing spec files: four
noUncheckedIndexedAccessviolations indexing a regex capture and a
Uint8Array, one uncheckedsources[0], and oneexactOptionalPropertyTypesviolation passingcandidate: undefinedexplicitly where omitting theproperty was meant. All six are fixed by narrowing, never by loosening a compiler option.
How it was verified
The guard fails on a real error. A deliberate
const x: number = "string"added to a spec makesit exit
1withtests/unit/render-json.spec.ts(66,7): error TS2322; removing it restores exit0,and the file was confirmed byte-identical afterwards.
Every suppression is real. Removing the
@ts-expect-errormarkers makes all five underlyingerrors surface — which is the only thing that shows the assertions are load-bearing rather than
decorative:
TS2558: Expected 0 type arguments, but got 1parseStrictJson<ConfigV1>is not expressibleTS2741: Property '[strictJsonBrand]' is missingStrictJsonDocumentcannot be built from a literalTS2739: 'StrictJsonDocument' is missing … from 'ConfigV1'TS2739: 'LockV1' is missing sources, platformsvalidate('lock', …)is notConfigV1TS2345: '"lock"' is not assignable to '"config"'That is packet 4B step 5's three requirements and packet 5B step 5's three, and it closes the last of
the Phase 1 deferred items.
Full gate, every command exiting
0against the committed tree:Deliberately not included
No branch-protection change. The new step runs inside the
Node 24 (pnpm)job, which is alreadya required check on
ubuntu-latestandwindows-latest; a failing step fails that job. Adding afourth required check would mean splitting it into its own job for no additional guarantee.
No changelog fragment. Build tooling and test corrections ship no behaviour change, and
changelog.d/stays dormant until Stage 1 Phase 5 completes.No product code.
packages/ai-tooling/src/**is untouched; only test sources changed under thattree.