fix(spec): 让 spec 测试层真的进 tsc,@ts-expect-error 退役 pin 不再是幽灵检查 - #5478
Conversation
…ns stop being phantom checks `packages/spec/tsconfig.json` excludes `**/*.test.ts` and the package's `typecheck` script is a bare `tsc --noEmit` reading that same config, so no gate anywhere read a spec test file with a type checker. Seventeen `@ts-expect-error` retirement pins across five files evaluated never: deleting a directive line left every gate green. - `packages/spec/tsconfig.test.json`: a sibling of the build config (which keeps its exclusion — ci.yml gates that no test file reaches the published artifact) with vitest's module semantics (`module: esnext`, `moduleResolution: bundler`, ES2022 lib). Strictness flags are inherited, untouched. - 39 errors in the five pin files fixed outright, plus the real defects the compile surfaced: two missing `z` imports (TS2503), an `as const[]` typo (TS2304), a `session.tenantId` fixture key removed in v11 (#3290), and two `@ts-expect-error` directives sitting three lines above the error they claim to suppress. - Residual test-layer debt (79 files / 691 errors, mostly fixture literals typed with `z.infer` while holding `z.input` values) is held per file in `test-typecheck-debt.json`, an EXACT ratchet re-measured by tsc on every run. - `check-type-check-coverage.mjs`: TESTS_COVERED now reads every tsconfig a package's typecheck chain names, so the sibling-config repair graduates a package instead of leaving it in TEST_DEBT forever; spec's stale entry (272 files / 902 errors; actual 295 / 842) is deleted accordingly. New repo-wide PINS_CHECKED invariant: a `@ts-expect-error` outside every tsc program fails, with a closed shrink-only PHANTOM_PIN_DEBT baseline. Fixes #5286 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
…ntom-pin baseline at its issue - tsconfig.test.json gains the reasoning it was missing: what differs from the build config (module semantics only) and what deliberately does not (strictness, inherited untouched). `include` stops at `src`, and the comment says why: `packages/spec/scripts/**` is in no tsconfig at all — a second, differently-shaped hole, measured at 16 files / 33 errors and filed as #5475 rather than ridden along here. No `@ts-expect-error` hides there. - PHANTOM_PIN_DEBT's metadata-core entry now names #5476, the issue that closes it. Its cause differs from spec's: no exclusion names the file, it simply sits outside `include`, which is why TESTS_COVERED could never see it either. - Drop an unused constant from check-test-typecheck.mts (found by compiling the scripts layer during the measurement above). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…rated ledger
`check:generated --reconcile-only` — the required meta-gate in lint.yml's
TypeScript Type Check job — went red on this branch. `tsc --noEmit` passed; the
step after it did not. package.json declared `check:test-typecheck` and
`gen:test-typecheck-debt`, and no bucket in check-generated.ts named either:
`check:test-typecheck` exists in package.json but is in neither GATED nor NO_GENERATOR.
`gen:test-typecheck-debt` exists in package.json but no GATED entry names it [...]
They are a GATED pair. The gate compares a checked-in artifact
(test-typecheck-debt.json) against what `tsc -p tsconfig.test.json` measures
right now, and `gen:test-typecheck-debt` is that artifact's writer — so
NO_GENERATOR ("no artifact to regenerate") and UNGATED_GENERATORS ("nothing
verifies this output") would each have been false, in opposite directions.
What did NOT fit the existing shape is `--fix`. Every other GATED artifact is a
pure function of the source, so regenerating is always the right answer. This one
records DEBT, and its four verdicts split two ways: "the debt shrank" and "the
file graduated" mean re-record, while "the debt grew" and "an unledgered file has
errors" mean fix the code. `--fix` regenerates without reading which one it got,
and the gate's own failure text says the ledger "only ratchets down" — so a blind
`--fix` would contradict the gate it is fixing and launder new debt in as a
mechanical diff, the same hazard that keeps dual-source-exports.baseline.json out
of GATED entirely (#4446). Hence a `ratchet` flag: the entry is GATED and
reported like every other, and `--fix` refuses it, printing the gate's own
prescription instead of guessing.
This is not hypothetical — merging main into this branch brought three new spec
test files in. They happened to compile clean (the ledger stayed byte-identical
at 79 files / 691 errors), but had any of them carried errors, a reflexive `--fix`
would have ledgered them silently.
Same reasoning adds the artifact to NOT_DRIVER_MANAGED, beside
docs-import-surface.baseline.json: a merge driver must not recompute a
shrink-only ratchet either.
Also pins the reconciliation itself in the spec suite. It had already been
dormant or unsatisfied three times (#4177, #4232, and this branch), each costing
a CI lap because `pnpm test` never read the ledger.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
修 CI:把两个新脚本登记进
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31020108661 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
分诊(spec 车道 PM,
Generated by Claude Code |
|
回填单号:上面分诊承诺的 mongodb flake 立单经查重命中已有单 #5517(engine 队列,rename 竞态诊断更完整),未另立;两次命中数据已追记至该单。 Generated by Claude Code |
Fixes #5286
问题
packages/spec/tsconfig.json的exclude含"**/*.test.ts",而该包的typecheck脚本就是裸tsc --noEmit(读同一份 tsconfig)。于是 没有任何 gate 用类型检查器读过 spec 的测试文件:vitest 走 esbuild 只剥类型不解析,CI 里也没有第二个把 spec 测试纳入编译的步骤。树上 5 个文件、17 条
@ts-expect-error(其中相当一部分是退役 pin,spec-property-retirement playbook 倚重的 “tsc 是最好的清扫器” 通道)因此从未被求值——删掉指令行,所有 gate 依旧全绿。这就是 phantom check。前提在
origin/main@01c0baef9复核仍然成立(exclude 原样、17 条指令原样)。处置(PM ruling A1)
1.
packages/spec/tsconfig.test.jsonbuild 用的
tsconfig.json不动——它的 exclude 有理由存在(ci.yml 有「编译产物不得含测试文件」gate)。新增一份 sibling 配置,由typecheck脚本显式指名:它只带 module 语义(
module: esnext、moduleResolution: bundler、lib含 ES2022),对齐 vitest 的真实执行方式;strict 系严格度标志一律继承、未做任何放宽。这是保真,不是放水:在 build 的 NodeNext 语义下,842 条原始错误里有 108 条是「检查本身配错了」(TS2835 x58 动态 import 缺.js、TS1470 x24import.meta、TS2307 x18、TS2550 x7),先修配置再读残差是 #4311 已经写下的纪律。2. 直接修掉的真实缺陷
5 个 pin 文件的 39 处错误全部修完(现在 0 错误,任何一条 pin 都真的会红),另加编译一开就浮出来的实缺陷:
api/endpoint.test.ts、api/router.test.tsz.却从未 import(TS2503)kernel/package-artifact.test.ts:154as const[]—— TS 读作「名为const的类型的数组」,TS2304data/hook.test.tssession.tenantId—— 这个别名在 v11 就删了(#3280/#3290),blessed 名是organizationId;parse会静默剥掉未知键,所以 vitest 永远看不见system/translation-typegen.test.ts:102/:128kernel/plugin-runtime-retirement.test.tsimport('./plugin-runtime.zod')一旦被编译就是 TS2307;改成变量说明符,运行时断言(load 必须 reject)分毫未动data/object.test.tsServiceObject(z.infer,默认值已生效)却装着作者手写的输入 —— 改为已有的ServiceObjectInput(z.input)3. 残余债:每文件、精确、只减不增的台账
其余 79 个文件 / 691 条错误(绝大多数是上面那类
z.infervsz.input的 fixture 字面量)不在本 PR 手改,落进packages/spec/test-typecheck-debt.json,由scripts/check-test-typecheck.mts每次跑 tsc 重新测量后逐文件比对:@ts-expect-error正好落在这里)5 个 pin 文件不在台账里,所以它们的任何一条错误都是硬红。
4.
scripts/check-type-check-coverage.mjstsconfig.json,sibling 测试配置无论怎么接都看不见。现在读包里全部tsconfig*.json,并要求覆盖测试的那份被typecheck脚本链指名(可穿透一层pnpm脚本间接调用)——「有这份文件」不算覆盖,「跑了这份文件」才算。@ts-expect-error的测试文件不得落在任何 tsc program 之外。逃生舱PHANTOM_PIN_DEBT只减不增、且对新条目关闭,seed 两条:packages/client/src/client.test.ts(tsconfig 排除测试;留给 幽灵@ts-expect-error不止 spec:@objectstack/client也有 1 处落在 tsconfig 排除区内(全仓横扫结果) #5449)packages/metadata-core/test/types.test.ts—— 扫描时发现的第二例,成因不同且没有任何 exclude 指向它:include是["src/**/*"]而文件在 siblingtest/树里,所以连 TESTS_COVERED 都数不到它(testFiles为 0)。用tsc --showConfig核实过其 program 里确实没有这个文件。5. 注释校正(21 + 5 个文件)
仓里约 20 处注释写着「本包里的 compile-time pin 是 no-op,因为 tsconfig 排除了测试」——本 PR 之后这句话变成假的,而且它明确劝阻后来者写类型层 pin。逐处改成过去时并点名 #5286;结论(「所以 load-bearing 的是 compiler-API / runtime pin」)大多仍然成立,因为
keyof typeof import(...)只枚举 VALUE 导出(#4642),类型-only 的退役仍需 compiler-API walk。filter-array-declaration.test.ts顶部那段「这些断言不在 CI 里跑」的大注释整段重写。(第一版机械替换误伤了无关的测试标题,如「an empty batch is a no-op」;已整体回滚,改为带上下文窗口约束的替换后重做。)
#4642 并案
ruling 2 要求验证:
typeof import(...)形态里断言 value 导出 的两条 pin,在测试进入 tsc 后是否真的会红。先证红(见下)确认ui/notification.test.ts的 pin 现在真的生效。shared/retry-policy.test.ts的说明里,「keyof typeof import(...)只枚举 value 导出」这半个论据不受本 PR 影响,已保留并标注。spec 测试里没有发现新的 bare-type-name 条件式 pin。验证(全部为实跑输出)
(1) 删任一存量 pin 指令行 → 红,恢复 → 绿
删掉
object.test.ts的// @ts-expect-error — compactLayout was retired (#2536):被我移动过的那条指令同样是 load-bearing 的(删掉后):
恢复后:
check:test-typecheck: OK — 79 file(s) / 691 error(s)。(2) 新不变式先证红
把
packages/client/src/client.test.ts从PHANTOM_PIN_DEBT拿掉:反向(RECONCILED):给一个已被编译的 spec pin 文件加条目 →
把
tsconfig.test.json从 typecheck 链里摘掉(即回到 main 的接线)→ 6 条问题,正是本单描述的状态:(3) 台账只减不增实测(三个方向一次跑出)
(4) #4642 pin 先证红 —— 把退役的
NotificationSchemavalue 导出加回ui/notification.zod.ts:(5) 全绿基线
不加 changeset 的理由
本 PR 不改任何发布物的运行时行为:改动落在测试文件、
tsconfig.test.json、scripts/、仓库 gate 与package.json的 scripts 段;packages/spec的files白名单为dist / json-schema / liveness / prompts / llms.txt / README.md / src/**/*.zod.ts / CHANGELOG.md / api-surface.json / spec-changes.json,其中src/**/*.zod.ts一个字节未动。没有 schema、类型或 API 面变化可写进 release notes。越界发现(已开单,不夹带进本 PR)
packages/spec/scripts/**在任何 tsconfig 的 include 之外:9 个 vitest 测试文件 + 全部构建/gate 脚本无类型检查覆盖 #5475 ——packages/spec/scripts/**整体不在任何 tsconfig 的include里,而 vitest 会跑其中 9 个*.test.ts,其余是gen:schema/check:liveness等 20 多个 gate 脚本本体。实测把它并进来是 16 文件 / 33 错误(TS5097 x13、TS2593/TS2304 x2、TS7016 x1 属 config 层;build-schemas.ts:1208-1228的 TS2339 x5 + TS7006 是真 code-tier;TS6133 x7 噪声)。我一度把它并进tsconfig.test.json,量出来后判断越界,已回退,并在该文件注释里写明边界。确认过:那批文件里没有任何@ts-expect-error,所以 PINS_CHECKED 不受影响。packages/metadata-core/test/types.test.ts的@ts-expect-error是幽灵检查:文件在include: ["src/**/*"]之外,没有任何 exclude 指向它 #5476 ——packages/metadata-core/test/types.test.ts的@ts-expect-error同为幽灵检查,但成因不同:没有任何 exclude 指向它,它只是落在include: ["src/**/*"]之外,所以连 TESTS_COVERED 都数不到(该包testFiles为 0)。已作为PHANTOM_PIN_DEBT的 seed 之一记账并指向该单。Generated by Claude Code