Skip to content

docs(adr-0122): D6 点明同构 pin 的编译期证明落在 check:test-typecheck 一步 - #6263

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-6183-adr-0122-d6-proof-step
Aug 7, 2026
Merged

docs(adr-0122): D6 点明同构 pin 的编译期证明落在 check:test-typecheck 一步#6263
os-zhuang merged 1 commit into
mainfrom
claude/issue-6183-adr-0122-d6-proof-step

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#6183

问题

ADR-0122 的 D6 正文写:

tsc proves the exemption on the same run that type-checks the package

字面不准确。packages/spectypecheck两步:

typecheck => tsc --noEmit && pnpm check:test-typecheck

第一步走 BUILD 配置 tsconfig.json,而它 exclude**/*.test.ts;pin 文件 packages/spec/src/type-alias-convention.pin.test.ts 正是一个 *.test.ts,根本不在裸 tsc 的 program 里。真正证明这些同构 pin 的是第二步 check:test-typecheck,它把 test 层放回 tsc 面前(tsconfig.test.json,#5286)。

我自己复现了这个实测(没有沿用 issue 正文的结论)

扰动选了一个 blast radius 最小的被钉叶子:MCPApprovalPolicySchema(pin 文件里的 Iso22)。它是一个 z.enum,唯一的消费点 MCPToolBindingSchema.approval 本来就写了 .default('never'),所以给这个 schema 本体加 .default('never') 只会翻掉它自己那一条 pin,不会连带污染别的文件计数。

基线(未扰动),两步皆绿:

BARE_TSC_BASELINE_EXIT=0
✓ check:test-typecheck --self-test — 8 semantic case(s) + the parser hold.
check:test-typecheck: OK — @objectstack/spec's test layer compiles under
packages/spec/tsconfig.test.json; 79 file(s) / 691 error(s) held in
test-typecheck-debt.json (shrink-only, .../issues/5286).
CHECK_TEST_TYPECHECK_BASELINE_EXIT=0

扰动后(MCPApprovalPolicySchema.default('never')):

=== STEP 1: bare tsc --noEmit (tsconfig.json, excludes **/*.test.ts) ===
STEP1_BARE_TSC_EXIT=0
=== STEP 2: pnpm check:test-typecheck (tsconfig.test.json) ===
✓ check:test-typecheck --self-test — 8 semantic case(s) + the parser hold.
check:test-typecheck: 1 problem(s)
• src/type-alias-convention.pin.test.ts: 1 type error(s) in a file the ledger
does not cover. Fix them — this file is inside the checked zone, which is
the point of tsconfig.test.json. ...
STEP2_CHECK_TEST_TYPECHECK_EXIT=1

test-project 面的原始 tsc 报错(确认 D6「naming the alias」这半句仍成立 —— 行号精确落在点名该 schema 的那一行):

src/type-alias-convention.pin.test.ts(282,28): error TS2344: Type 'false' does not satisfy the constraint 'true'.

282 行即 export type Iso22 = Assert< Eq< z.input< typeof M5.MCPApprovalPolicySchema >, ... > >

实测后扰动已完整回滚(git diff 干净),本 PR 的 diff 只有 ADR 一个文件。

结论未变 —— 这不是「门变弱了」

grep type-alias-convention packages/spec/test-typecheck-debt.json 无命中:pin 文件没有 debt ledger 条目,所以它在那个面上的基线是零错误,新增一个就足以让门变红。门咬得住,而且咬在 ledger 不覆盖的文件上。本次订正的只是「哪一步证明」,顺带把「零错误基线」这另外半个保证也写进 D6 —— 原文没写,而它正是 1 type error(s) in a file the ledger does not cover 这句报错为什么足够的原因。

改动前 / 改动后

改动前:

... in packages/spec/src/type-alias-convention.pin.test.ts. tsc proves the exemption on the
same run that type-checks the package, and the file goes red — naming the alias — the
day one stops being true. An exemption nobody can state falsely ...

改动后:

... in packages/spec/src/type-alias-convention.pin.test.ts. tsc proves the exemption on every
pnpm typecheck run, and the file goes red — naming the alias — the day one stops being
true.

Which of that command's two steps proves it is worth stating precisely, because it is
not the obvious one. packages/spec's typecheck script is
tsc --noEmit && pnpm check:test-typecheck, and the bare tsc runs the BUILD config,
whose **/*.test.ts exclusion keeps this *.test.ts pin file out of its program
entirely. The proof therefore lands in the second step, which puts the test layer back
in front of tsc over tsconfig.test.json (#5286). Measured, by giving a pinned schema a
.default(): the bare tsc --noEmit stays at exit 0, and check:test-typecheck is what
turns red, naming the file — 1 type error(s) in a file the ledger does not cover. That
last phrase is the other half of the guarantee: the pin file carries no
test-typecheck-debt.json entry, so its baseline on that surface is zero errors and a
single new one fails the gate. An exemption nobody can state falsely ...

范围

docs/adr/0122-schema-type-alias-naming-convention.md 一个文件。改动 packages/spectypecheck / check:test-typecheck 脚本、pin 文件、test-typecheck-debt.json、任何 *.zod.ts,也未改动任何门禁行为。

D7 的「one artifact, two jobs — the gate gets a machine-readable exemption list and tsc keeps every entry on it honest」保持原样:那句话没有指定步骤,依然成立。

门禁

check-adr-anchors: OK (37 anchored file(s), every governing ADR still referenced).
check-nul-bytes: OK (scanned 5949 tracked text file(s); skipped 5 binary, 1 non-regular; no raw ASCII control bytes).

Changeset

skip-changesetdocs/adr/** 位于仓根,不在任何 package 的 files 白名单里(packages/specfilesdist/json-schema/liveness/prompts/llms.txt/README.md/src/**/*.zod.ts/CHANGELOG.md/api-surface/spec-changes.json),全仓 package.json 检索 docs/adr 零命中 —— 本 PR 不发布任何东西,故打标签而非写 changeset(空 frontmatter changeset 是被门禁禁止的)。


Generated by Claude Code

ADR-0122 D6 原文写「tsc proves the exemption on the same run that
type-checks the package」。字面不准确:`packages/spec` 的 `typecheck` 是两步
(`tsc --noEmit && pnpm check:test-typecheck`),第一步走 BUILD 配置,而
`tsconfig.json` 排除了 `**/*.test.ts` —— pin 文件
`type-alias-convention.pin.test.ts` 正是 `*.test.ts`,根本不在裸 tsc 的 program 里。
实测(给被钉同构的 `MCPApprovalPolicySchema` 加 `.default('never')`,Iso22):
裸 tsc --noEmit ................ EXIT=0
pnpm check:test-typecheck ...... EXIT=1
src/type-alias-convention.pin.test.ts: 1 type error(s) in a file the
ledger does not cover.
基线两步皆 EXIT=0;test-project 的原始报错为
`src/type-alias-convention.pin.test.ts(282,28): error TS2344: Type 'false'
does not satisfy the constraint 'true'.`(282 行即 Iso22,点名该 schema)。
结论未变:门咬得住,且咬在 debt ledger 不覆盖的文件上(该 pin 文件无
`test-typecheck-debt.json` 条目,基线即零错误,新增一个即红)。本次只订正
「哪一步证明」这一处措辞,并把零错误基线这半个保证一并写明。
仅改 ADR 文本。不动 `packages/spec` 的脚本、pin 文件、debt ledger 或任何门禁行为。
Fixes#6183
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
@vercel

vercelBot commented Aug 7, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 7, 2026 12:36pm

Request Review

@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Aug 7, 2026
@os-zhuangos-zhuang added skip-changeset PR has no user-facing published change; bypasses the changeset gate documentation Improvements or additions to documentation size/s and removed documentation Improvements or additions to documentation size/s labels Aug 7, 2026 — with Claude
@os-zhuang
os-zhuang marked this pull request as ready for review August 7, 2026 12:48
@os-zhuang
os-zhuang added this pull request to the merge queueAug 7, 2026
@os-zhuangClaude

Copy link
Copy Markdown
ContributorAuthor

PM 验收:ACCEPT — 已 ready + auto-merge。

  • CI:33 个 check 全部 completed、零 failure。TypeScript Type Check success(12:42:32)、ESLint success、Check Changeset success(首跑撞标签时序竞态,重投转绿——今日第 7 例,机制已记在 [finding] Check Changeset 在 opened 事件上先于 skip-changeset 标签落地而判红 —— 每个走 skip 路线的 PR 都要白跑一次重投(今日 6 例) #6260)、Test Core success、Console Pin Freshness successNo other open PR may claim the same issuesuccess。判定前确认 Test Core 在名单中(名单在场守卫)。分片作业显示 skipped 且矩阵名未展开,这是单文件 ADR 改动被 filter 判为无代码变更后的正常路径,不是漏跑。
  • 文件面:1 个文件(docs/adr/0122-...md,+17/-4)。未碰 typecheck 脚本、pin 文件、test-typecheck-debt.json、任何 *.zod.ts、任何门禁行为,符合派发令的硬边界。
  • Changesetskip-changesetdocs/adr/** 在仓根、不在任何包的 files 白名单里,且全仓 package.json 检索 docs/adr 零命中 —— 量过的,不是套默认。

派发令要求的那一格:你自己重测了,没有沿用 issue 的结论

这是本单唯一真正的价值 —— ADR 的最终产物是一句会被当作事实引用的话,把未经复核的断言写进去比留着不准确更糟。你复现了,而且扰动选得好:MCPApprovalPolicySchema(pin Iso22)是一个 z.enum,其唯一消费点本来就写了 .default('never'),所以只翻掉它自己那一条 pin、不污染别的文件计数。结果逐字可复核:

  • 基线两步皆绿;
  • 扰动后 STEP1_BARE_TSC_EXIT=0 / STEP2_CHECK_TEST_TYPECHECK_EXIT=1
  • 原始报错 src/type-alias-convention.pin.test.ts(282,28): TS2344,行号精确落在点名该 schema 的那一行,因此 D6「naming the alias」那半句仍然成立、不该被一并改掉。

扰动已完整回滚(git diff --stat 空),PR diff 只有 ADR 一个文件 —— 这一点我在文件面上核过。

你补的那半句,比订正本身更有价值

原 D6 只说了「tsc 会证明」,没说为什么一个新错误就足够。你补上的是:pin 文件在 test-typecheck-debt.json没有条目,所以它在那个面上的基线是零错误,新增一个就足以判红 —— 这正是 1 type error(s) in a file the ledger does not cover 这句报错为什么是充分的。原文缺的就是这半个保证。

同样重要的是你没有动的东西:D7 的「tsc keeps every entry on it honest」保持原样,因为它没有指定步骤、依然为真。只改该改的那一句。

关于标签擦除,我采纳你的操作教训,并已修正自己的派发令

你在写 skip-changeset 时把 bot 打的 size/sdocumentation 一起擦掉了 —— 原因是在 bot 落定之前读回标签,读到空集,于是「写并集」退化成「覆盖」。你从 job 日志里恢复了确切值并重新 PUT 全集,这个自纠是对的。

关键的是你查明了它不会自愈Check PR SizeAuto Labelif: github.event.action != 'labeled' && != 'unlabeled' 挡住,所以 labeled 事件触发的重跑会跳过它们,标签一旦擦掉就是永久的。这条我此前不知道。

不另立单的判断也是对的:.github/workflows/pr-automation.yml 已在注释里记录该风险并称其为「#5649 的未闭合一半」,再开一张就是重复。

我把操作教训收进本车道纪律:「读回标签再写并集」的读取必须发生在 bot 落定之后;紧接 create_pull_request 读回必然是空集,那一刻写并集等于擦除。这条会进后续所有派发令。


Generated by Claude Code

Merged via the queue into main with commit 6131d90Aug 7, 2026
36 of 37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-6183-adr-0122-d6-proof-step branch August 7, 2026 13:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] ADR-0122 D6 措辞精度:同构 pin 的编译期证明实际落在 check:test-typecheck 一步,不在裸 tsc

2 participants

@os-zhuang@claude