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

fix(spec): 让 spec 测试层真的进 tsc,@ts-expect-error 退役 pin 不再是幽灵检查#5478
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.jsonexclude"**/*.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: esnextmoduleResolution: bundlerlib 含 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.tsapi/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 名是 organizationIdparse 会静默剥掉未知键,所以 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 标注为 ServiceObjectz.infer,默认值已生效)却装着作者手写的输入 —— 改为已有的 ServiceObjectInputz.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 的实际码是 TS2322compactLayout 的位置类型已收窄成 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.tsPHANTOM_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.jsonscripts/、仓库 gate 与 package.json 的 scripts 段;packages/specfiles 白名单为 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 protocol:system tests protocol:ui 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 protocol:system protocol:ui 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 protocol:system protocol:ui 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.mjsNOT_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(descriptionsubtitle) #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>
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 protocol:data protocol:system protocol:ui 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