Skip to content

fix(spec): 让 spec 测试层真的进 tsc,@ts-expect-error 退役 pin 不再是幽灵检查 - #5478

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-5286-spec-test-typecheck
Aug 5, 2026
Merged

os-zhuang merged 4 commits into
mainfrom
claude/issue-5286-spec-test-typecheck

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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.json

build 用的 tsconfig.json 不动——它的 exclude 有理由存在(ci.yml 有「编译产物不得含测试文件」gate)。新增一份 sibling 配置,由 typecheck 脚本显式指名:

"typecheck": "tsc --noEmit && pnpm check:test-typecheck"
"check:test-typecheck": "tsx scripts/check-test-typecheck.mts --self-test && tsx scripts/check-test-typecheck.mts --project tsconfig.test.json"

它只带 module 语义(module: esnext、moduleResolution: bundler、lib 含 ES2022),对齐 vitest 的真实执行方式;strict 系严格度标志一律继承、未做任何放宽。这是保真,不是放水:在 build 的 NodeNext 语义下,842 条原始错误里有 108 条是「检查本身配错了」(TS2835 x58 动态 import 缺 .js、TS1470 x24 import.meta、TS2307 x18、TS2550 x7),先修配置再读残差是 #4311 已经写下的纪律。

2. 直接修掉的真实缺陷

5 个 pin 文件的 39 处错误全部修完(现在 0 错误,任何一条 pin 都真的会红),另加编译一开就浮出来的实缺陷:

位置 缺陷
api/endpoint.test.ts、api/router.test.ts 类型位置用了 z. 却从未 import(TS2503)
kernel/package-artifact.test.ts:154 as const[] —— TS 读作「名为 const 的类型的数组」,TS2304
data/hook.test.ts fixture 里的 session.tenantId —— 这个别名在 v11 就删了(#3280/#3290),blessed 名是 organizationId;parse 会静默剥掉未知键,所以 vitest 永远看不见
system/translation-typegen.test.ts :102/:128 两条指令写在声明行上方,真正的报错在三行之下的属性上,指令自己反而 TS2578 unused
kernel/plugin-runtime-retirement.test.ts 断言「模块已消失」的字面量 import('./plugin-runtime.zod') 一旦被编译就是 TS2307;改成变量说明符,运行时断言(load 必须 reject)分毫未动
data/object.test.ts 7 处 fixture 标注为 ServiceObject(z.infer,默认值已生效)却装着作者手写的输入 —— 改为已有的 ServiceObjectInput(z.input)

3. 残余债:每文件、精确、只减不增的台账

其余 79 个文件 / 691 条错误(绝大多数是上面那类 z.infer vs z.input 的 fixture 字面量)不在本 PR 手改,落进 packages/spec/test-typecheck-debt.json,由 scripts/check-test-typecheck.mts 每次跑 tsc 重新测量后逐文件比对:

  • 未登记文件出现任何错误 → 红(日常情形:删掉一条 @ts-expect-error 正好落在这里)
  • 已登记文件错误变多 → 红(GREW)
  • 已登记文件错误变少 → 红,要求把数字往下重记(SHRANK)——这才是「只减不增」真正生效的地方
  • 已登记文件归零 → 红,要求删条目(GRADUATED)

5 个 pin 文件不在台账里,所以它们的任何一条错误都是硬红。

4. scripts/check-type-check-coverage.mjs

  • TESTS_COVERED 补上 :267 盲点:过去只读 tsconfig.json,sibling 测试配置无论怎么接都看不见。现在读包里全部 tsconfig*.json,并要求覆盖测试的那份被 typecheck 脚本链指名(可穿透一层 pnpm 脚本间接调用)——「有这份文件」不算覆盖,「跑了这份文件」才算。
  • spec 的 TEST_DEBT 条目按脚本的既定路径删除(它毕业了)。顺带说明:那条记录本来就是陈的(记 272 文件 / 902 错误,实际 295 / 842),债务没有消失而是搬进了上面那本按文件、由 gate 亲自测量的台账,比冻结的包级数字更强。
  • 新不变式 PINS_CHECKED(全仓):带 @ts-expect-error 的测试文件不得落在任何 tsc program 之外。逃生舱 PHANTOM_PIN_DEBT 只减不增、且对新条目关闭,seed 两条:

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):

src/data/object.test.ts(944,11): error TS2322: Type 'string[]' is not assignable to type 'never'.

check:test-typecheck: 1 problem(s)
  - src/data/object.test.ts: 1 type error(s) in a file the ledger does not cover. ...
ELIFECYCLE Command failed with exit code 1.

注:dispatch 模板预设的是 TS2578/TS2694;这条 pin 的实际码是 TS2322(compactLayout 的位置类型已收窄成 never),方向仍是「删指令 → 红」。如实记录,不套模板。

被我移动过的那条指令同样是 load-bearing 的(删掉后):

src/system/translation-typegen.test.ts(108,7): error TS2741: Property 'email' is missing in type '{ name: { label: string; }; }' but required in type 'StrictFieldTranslations< ... >'.

恢复后:check:test-typecheck: OK — 79 file(s) / 691 error(s)。

(2) 新不变式先证红

把 packages/client/src/client.test.ts 从 PHANTOM_PIN_DEBT 拿掉:

check-type-check-coverage: 1 problem(s)
  - packages/client/src/client.test.ts: carries a `@ts-expect-error` directive but no tsc program the `typecheck` script runs compiles it, so the directive is never evaluated ...

反向(RECONCILED):给一个已被编译的 spec pin 文件加条目 →

  - PHANTOM_PIN_DEBT entry for "packages/spec/src/data/object.test.ts" is no longer an unchecked pin ... That is the ratchet: this list only shrinks.

把 tsconfig.test.json 从 typecheck 链里摘掉(即回到 main 的接线)→ 6 条问题,正是本单描述的状态:

  - @objectstack/spec (packages/spec): tsconfig.json excludes its own test files, hiding 295 of them from `tsc --noEmit` ...
  - packages/spec/src/contracts/sharing-service.test.ts: carries a `@ts-expect-error` directive but no tsc program ...
  - packages/spec/src/data/filter-array-declaration.test.ts: ...
  - packages/spec/src/data/hook.test.ts: ...
  - packages/spec/src/data/object.test.ts: ...
  - packages/spec/src/system/translation-typegen.test.ts: ...

(3) 台账只减不增实测(三个方向一次跑出)

check:test-typecheck: 3 problem(s)
  - src/ai/model-registry.test.ts: 24 type error(s), ledger records 25 — the debt SHRANK, which is the goal. Re-record it ...
  - src/ai/skill.test.ts: 3 type error(s), ledger records 1 — the debt GREW. Fix the 2 new one(s) ...
  - src/data/hook.test.ts: ledger records 3 type error(s) but tsc reports none — it GRADUATED ...

(4) #4642 pin 先证红 —— 把退役的 NotificationSchema value 导出加回 ui/notification.zod.ts:

src/ui/notification.test.ts(76,11): error TS2322: Type 'false' is not assignable to type 'true'.

(5) 全绿基线

pnpm --filter @objectstack/spec typecheck   -> exit 0
   check:test-typecheck: OK — 79 file(s) / 691 error(s) held in test-typecheck-debt.json
pnpm --filter @objectstack/spec test        -> Test Files 310 passed (310) / Tests 7934 passed (7934)
node scripts/check-type-check-coverage.mjs --self-test -> 22 semantic + 11 observation case(s) hold
node scripts/check-type-check-coverage.mjs  -> OK (62/77 covered; test layer: 20 packages, 380 files)
node scripts/check-nul-bytes.mjs            -> OK (5445 files, no raw NUL)
pnpm turbo run typecheck --concurrency=2    -> Tasks: 124 successful, 124 total

不加 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)


Generated by Claude Code

claude added 2 commits August 5, 2026 13:14
…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
@vercel

vercel Bot commented Aug 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 5, 2026 3:11pm

Request Review

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file protocol:data tests tooling labels Aug 5, 2026
@os-zhuang os-zhuang added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed documentation Improvements or additions to documentation size/xl dependencies Pull requests that update a dependency file protocol:data labels Aug 5, 2026 — with Claude
@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file protocol:data size/xl labels Aug 5, 2026
…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

Copy link
Copy Markdown
Contributor Author

修 CI:把两个新脚本登记进 check:generated 台账 (77fc49e)

上一轮 “TypeScript Type Check” job 红的不是 tsc(那步过了),是它后面那步
pnpm --filter @objectstack/spec check:generated --reconcile-only:本 PR 往
packages/spec/package.json 加了 check:test-typecheck / gen:test-typecheck-debt,
却没在 packages/spec/scripts/check-generated.ts 的三本台账里给它们分类。

分类结论:GATED 对,不是 NO_GENERATOR、也不是 UNGATED_GENERATORS

这个 gate 拿签入的产物(test-typecheck-debt.json)去比 tsc -p tsconfig.test.json
当场量出来的结果,而 gen:test-typecheck-debt 正是这个产物的写入者。所以
NO_GENERATOR(“没有产物可重生成”)和 UNGATED_GENERATORS(“没有任何东西校验它的输出”)
两个都会是假话,方向还相反。按 reconciler 自己那句提问 —— “does it compare a
checked-in artifact against a generator, or audit source?” —— 答案是前者。

但 --fix 的语义对不上,所以加了 ratchet 标记

GATED 里其他产物都是源码的纯函数,重生成永远是对的答案。这一本记的是债,四种
判决分成两类:

  • “债变少了” / “文件毕业了” -> 重记数字(gen: 是对的)
  • “债变多了” / “未登记文件出现错误” -> 改代码(gen: 是错的)

--fix 不读判决就重生成,而 gate 自己的报错原文写着 the ledger “only ratchets
down” —— 盲跑 --fix 会跟它要修的那个 gate 自相矛盾,把新债当机械 diff 洗进去。
这正是 dual-source-exports.baseline.json 干脆不进 GATED 的理由(#4446);那本能靠
手改是因为只有几行,这本有 79 个文件,所以留生成器、把拒绝放在 --fix 里:

✗ 1 of 10 artifact(s) stale:

  test-typecheck-debt.json
    pnpm --filter @objectstack/spec gen:test-typecheck-debt   ← only if check:test-typecheck asked you to RE-RECORD; --fix will not run this one

--fix: regenerating 0 of the 1 stale artifact(s) — the rest are ratchets, refused below.

  ✗ gen:test-typecheck-debt — REFUSED

拒绝之后台账字节未变(md5 实测),再手跑 gen: 恢复到与提交版完全一致。

不是假想风险:这次 git merge origin/main 带进来三个新的 spec 测试文件(它们恰好
干净,台账仍是 79 files / 691 errors 字节不变),但只要其中任何一个带错误,一次条件
反射式的 --fix 就会把它们悄悄记进台账。

同理把该产物登记进 scripts/regen-artifacts.mjs 的 NOT_DRIVER_MANAGED,与
docs-import-surface.baseline.json 并列:合并驱动同样不该重算一本只减不增的台账。

顺带把这条 reconciliation 钉进 spec 测试套件

它已经第三次失效或不满足了(#4177、#4232、以及本分支),每次代价都是一整圈 CI,原因
是 pnpm test 从不读这本台账。新增 packages/spec/scripts/check-generated-ledger.test.ts
(实跑 --reconcile-only,1.6s,只读)。

实跑验证

pnpm --filter @objectstack/spec check:generated --reconcile-only
  ✓ ... 18 check: + 12 gen: scripts, all classified (10 gated, 7 source audits, 2 ungated generators, 1 aggregate).

反向:把新 GATED 条目摘掉再跑 -> exit 1,复现 CI 原文两条
  `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 ...

pnpm --filter @objectstack/spec check:generated   -> ✓ All 10 generated artifacts are up to date.
pnpm --filter @objectstack/spec typecheck         -> exit 0(含 check:test-typecheck: OK — 79 files / 691 errors)
pnpm --filter @objectstack/spec test              -> Test Files 313 passed (313) / Tests 7965 passed (7965)
pnpm check:merge-driver                           -> ✓ 9 path(s) agree, 7 deliberately excluded
node scripts/check-nul-bytes.mjs                  -> OK (5466 files, no raw control bytes)
eslint(三个改动文件)                              -> exit 0

按 PM 说明,skip-changeset 已在单上,本次不加 changeset。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 5, 2026 15:23
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31020108661 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (2/3) — 失败步骤: Run this shard's tests

    �[90mstderr�[2m | src/sql-driver-unique-tenancy.test.ts�[2m > �[22m�[2mSqlDriver unique × tenancy (#3696)�[2m > �[22m�[2mretires a legacy global unique index and replaces it with the composite
    �[90mstderr�[2m | src/sql-driver-unique-tenancy.test.ts�[2m > �[22m�[2mSqlDriver unique × tenancy (#3696)�[2m > �[22m�[2mretires the legacy `uniq_<table>_<col>` index left by the drift rebuild path
    �[90mstderr�[2m | src/sql-driver-unique-tenancy.test.ts�[2m > �[22m�[2mSqlDriver unique × tenancy (#3696)�[2m > �[22m�[2mbare-composite tightening + duplicate pre-flight (ADR-0120 D4)�[2m > �[22m�[2ma
    �[90mstderr�[2m | src/sql-driver-unique-tenancy.test.ts�[2m > �[22m�[2mSqlDriver unique × tenancy (#3696)�[2m > �[22m�[2mbare-composite tightening + duplicate pre-flight (ADR-0120 D4)�[2m > �[22m�[2mB
    �[22m�[39m[schema-drift] product: cannot tighten 'uniq_product_organization_id_code' as UNIQUE (COALESCE(organization_id, '__global__'), code) — existing rows already violate the NULL-safe unique cons
    �[90mstderr�[2m | src/sql-driver-unique-tenancy.test.ts�[2m > �[22m�[2mSqlDriver unique × tenancy (#3696)�[2m > �[22m�[2mbare-composite tightening + duplicate pre-flight (ADR-0120 D4)�[2m > �[22m�[2mB
    �[22m�[39m[schema-drift] REFUSING to rebuild 'uniq_product_organization_id_code' on 'product' as a NULL-safe unique — 1 duplicate group(s) violate it (e.g. organization_id="__global__", code="DUP" × 2
    

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 12 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

分诊(spec 车道 PM,session_018fxLGQdatPbBUvCgiVxg6D):第 2 型 —— 基础设施 flaky,与本 PR 无关,已核完整日志,原样重投。

  • triage bot 摘录的 sql-driver-unique-tenancy 行是通过用例的预期 stderr(那些 REFUSING 消息正是该套件要断言的输出),不是失败点。完整日志里的真实失败:driver-mongodb 套件 290 passed | 37 skipped | 0 failed,exit 1 来自 1 个 unhandled rejection —— mongodb-memory-server 拉 8.2.6 二进制超时(fastdl 不可达/挂起)后,被放弃下载流晚到的 ENOENT rename '….tgz.downloading'。
  • 同签名今天第二次(第一次在 PR feat(spec): 登记 ADR-0087 D2 conversion page-header-subtitle-alias(description → subtitle) #5509 的 Temporal Conformance job,同样 0 真实失败)。跳过路径的初衷是让二进制不可用可容忍,但弃流的 rejection 让它照样红 —— 骨架缺陷,已定为立单项(落点 driver-mongodb 测试骨架,engine 车道),配额恢复后查重立单并回填单号。
  • 处置:原样重投(重挂 auto-merge)。flaky 检索者:签名是 MongoBinaryDownload.js:413 ENOENT rename,与 sql-driver 无关,请按此归档。

Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

回填单号:上面分诊承诺的 mongodb flake 立单经查重命中已有单 #5517(engine 队列,rename 竞态诊断更完整),未另立;两次命中数据已追记至该单。


Generated by Claude Code

Merged via the queue into main with commit 7617956 Aug 5, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5286-spec-test-typecheck branch August 5, 2026 16:00
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
… 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>
os-zhuang pushed a commit that referenced this pull request Aug 27, 2026
`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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@ts-expect-error 退役 pin 在 packages/spec 里是幽灵检查:tsconfig 把 **/*.test.ts 排除出唯一的 tsc --noEmit

2 participants