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 |
…stops being a phantom check (objectstack-ai#5546) `packages/client/tsconfig.json` excludes `**/*.test.ts` and the package's `typecheck` script was a bare `tsc --noEmit` reading that same config, so no gate anywhere read a client test file with a type checker. The one `@ts-expect-error` in the package — `client.test.ts:1283` — evaluated never: on `origin/main`, deleting the directive line left `pnpm --filter @objectstack/client typecheck` just as green as leaving it there. Compiled for the first time, that directive reports TS2578 "unused". It never had anything to suppress: `project(environmentId: string)` accepts `''`, which is a perfectly good `string`, and the directive's own comment already said what the test proves — the empty id is rejected at RUNTIME. So the repair is to delete the directive, not to keep it: the reverse verification is the mirror of the usual one, and RESTORING the line is what now goes red. - `packages/client/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) and `rootDir` widened to the workspace root, since four test files deep-import sibling packages' route ledgers. Strictness flags are inherited, untouched. - 13 errors surfaced; eight were the tests' own and are fixed here — two unused imports, an unused parameter, two possibly-undefined reads on an optional `routes` map, an `unknown` payload now asserted with `toMatchObject` instead of cast, a `reference_to` key the field schema never had (the lookup declared no target at all), and the phantom pin. Re-spelling that key uncovered one more of the remaining kind; the six that stay are one producer-side defect (objectstack-ai#5543) held per file in `test-typecheck-debt.json`, EXACT and shrink-only. - `scripts/check-test-typecheck.mts`: PROMOTED from `packages/spec/scripts/`, parameterized with `--package`, so client onboards by wiring its `typecheck` script rather than by copying 300 lines. spec's ledger and its 79 files / 691 errors are unchanged, and the generated `_comment` is byte-identical. - Both graduations the gates force: `@objectstack/client` leaves TEST_DEBT (its stale entry measured 15 files / 19 errors, five of them the inherited-rootDir TS6059 that were the check's own misconfiguration) and its `PHANTOM_PIN_DEBT` seed — the entry objectstack-ai#5478 left addressed to this issue — is deleted. Fixes objectstack-ai#5449 Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh Co-authored-by: os-zhuang <jack@objectstack.ai> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… two dead branches, never produced (objectstack-ai#5050) (objectstack-ai#5621) `session.roles` on the runtime hook context had neither end: declared in `data/hook.zod.ts`, read only by the two plugin-approvals admin exemptions deleted in objectstack-ai#4839 (PR objectstack-ai#5049), and never written by `buildSession()` or anything else feeding a HookContext. ADR-0049 enforce-or-remove disposition: REMOVE. - tombstoned with `retiredKey()` (HookContextSchema is deliberately not `.strict()`, so a plain delete would strip the key silently — objectstack-ai#3733/ADR-0104) - placed BELOW the live keys: the reference generator renders a `z.never()` as `any` inside an inline shape summary, so in its original 4th position it made `references/data/hook.mdx` advertise `roles?: any` (renderer gap filed objectstack-ai#5606) - ADR-0087: a SemanticMigration (`hook-context-session-roles-retired`), NOT a D2 conversion — a HookContext is built per operation and never stored, so no source exists to rewrite (the `openApi31` / `activationEvents` shape) - pins both channels: the parse prescription and two `@ts-expect-error` directives, live since objectstack-ai#5286/objectstack-ai#5478 put the test layer in front of tsc - skills/objectstack-data hook reference no longer teaches the dead key Cross-repo consumer check ran in both directions (cloud/objectui, objectstack-ai#4895's discipline). The action body's `ctx.session` is a different, untyped object that does carry `roles` — named explicitly here and filed as objectstack-ai#5613 so it is not mistaken for a producer of this key. Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D Co-authored-by: Claude <noreply@anthropic.com>
`LEDGER_COMMENT` named the #5478/#5543 OUTPUT-vs-INPUT conflation as the cause of every ledgered error. PR #6786 fixed that on 2026-08-08; `packages/rest`'s ledger was created 18 days later and stamped with it in the present tense. Measured on 2026-08-27, both packages with a ledger that has entries: rest — 0 `z.infer` occurrences across its ledgered files spec — 8 across its 55 ledgered files, 5 of them inside prose comments; of its 263 errors, 9 are of the missing-properties shape at all, and none of those 9 sits in a file that contains a `z.infer` So the clause is dropped rather than made per-package: the mechanism sentences (EXACT ratchet, the three red directions, the regenerate command) are the part that is true everywhere, and the comment now tells the reader to measure the classes instead of handing them one. The second half of the defect is that `--update` rebuilds `_comment` from the constant, so a correction written into a ledger survives only until the next `gen:test-typecheck-debt` — which the ratchet requires on every repair. That is not hypothetical: `packages/client`'s `_comment` carries an appended AUTHORED paragraph recording that ADR-0122 phase 2 (#6083) emptied its ledger, and the next regeneration would have deleted it. The generated and authored halves are now separate keys: `_comment` is regenerated and says so, `_note` is authored and is preserved verbatim. No ledger NUMBER moves: `entries` is byte-identical to its parent in all three files (spec 55/263, rest 3/6, client 0/0), and spec's was rewritten by the real regenerator, not by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfaSTikked61BkcsB5Rn69
…e over its own prose (objectstack-ai#12809) * fix(devx): stop the test-typecheck ledger regenerating a refuted cause `LEDGER_COMMENT` named the objectstack-ai#5478/objectstack-ai#5543 OUTPUT-vs-INPUT conflation as the cause of every ledgered error. PR objectstack-ai#6786 fixed that on 2026-08-08; `packages/rest`'s ledger was created 18 days later and stamped with it in the present tense. Measured on 2026-08-27, both packages with a ledger that has entries: rest — 0 `z.infer` occurrences across its ledgered files spec — 8 across its 55 ledgered files, 5 of them inside prose comments; of its 263 errors, 9 are of the missing-properties shape at all, and none of those 9 sits in a file that contains a `z.infer` So the clause is dropped rather than made per-package: the mechanism sentences (EXACT ratchet, the three red directions, the regenerate command) are the part that is true everywhere, and the comment now tells the reader to measure the classes instead of handing them one. The second half of the defect is that `--update` rebuilds `_comment` from the constant, so a correction written into a ledger survives only until the next `gen:test-typecheck-debt` — which the ratchet requires on every repair. That is not hypothetical: `packages/client`'s `_comment` carries an appended AUTHORED paragraph recording that ADR-0122 phase 2 (objectstack-ai#6083) emptied its ledger, and the next regeneration would have deleted it. The generated and authored halves are now separate keys: `_comment` is regenerated and says so, `_note` is authored and is preserved verbatim. No ledger NUMBER moves: `entries` is byte-identical to its parent in all three files (spec 55/263, rest 3/6, client 0/0), and spec's was rewritten by the real regenerator, not by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfaSTikked61BkcsB5Rn69 * fix(devx): keep the new self-test off Object.hasOwn (root lib is ES2020) `scripts/**` is inside the ROOT tsc program, whose `lib` is ES2020, so `Object.hasOwn` is a TS2550 there and every use is a raw error the check:type-check-debt ratchet counts. The file already spends one in `evaluate()`; a second, added by the new `_note` pin, put the root program one over. Measured single-file under the root config: 29 diagnostics at the parent commit, 30 with `Object.hasOwn`, 29 with `in` — and the diagnostic SET is identical to the parent, not merely the count. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfaSTikked61BkcsB5Rn69 --------- Co-authored-by: Claude <noreply@anthropic.com>
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