fix(spec): build-schemas.ts --check 不再写 json-schema.manifest.json (#4711) - #4724
Merged
Merged
Conversation
The manifest ratchet in scripts/build-schemas.ts had no CHECK discriminator. `check:authorable-surface` (build-schemas.ts --check) — one of the eight generated-artifact gates `check:generated` runs — recomputed the emitted schema set and, on any addition or renamed-away def, rewrote the tracked json-schema.manifest.json in place and exited 0. Two defects, one missing `if`: 1. A check edited the working tree. Whatever the file held locally was overwritten by a command whose entire job is to look, which is how a `git stash pop` / worktree / merge-conflict operation fails for a reason nobody traces back to a gate. It is the #4675 merge-driver trap from the other side too: run any check mid-merge and a manifest computed from a half-merged tree lands on disk. 2. The additions branch could never go red in CI. Seven of the eight artifacts mean "stale => fail, run the generator"; this one meant "stale => I'll write it for you", inside the same `check:generated` summary. The ratchet is now isomorphic to the authorable-surface ratchet immediately below it: in --check it prints the unrecorded keys plus the `gen:schema` remedy and exits 1; outside --check it writes exactly as before. The `missing` branch (a published schema disappeared) is untouched — it already exited 1. New runtime e2e tests (scripts/build-schemas-check-mode.test.ts) pin the exit code AND the file bytes for all three branches, plus a negative control so "always red in check mode" cannot pass. They spawn the real script in a temp sandbox that copies scripts/ and symlinks src/, node_modules and package.json, so no test-only seam is added to the gate and a concurrent `gen:schema` from a turbo build cannot race the repo's tracked manifest. Runtime rather than compile-time because this package type-checks neither scripts/ nor *.test.ts (#4642). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckNo hand-written docs reference the 0 changed package(s). ✅ |
os-zhuang
marked this pull request as ready for review
August 2, 2026 23:00
This was referenced Aug 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4711
packages/spec/scripts/build-schemas.ts的 manifest ratchet 段落没有CHECK判别:check:authorable-surface(即build-schemas.ts --check)一旦算出新增的 def 或仍挂在manifest 上的 renamed-away def,就无条件重写 tracked 的
json-schema.manifest.json并 exit 0。一个
if缺失,两个后果:git stash pop、worktree、合并冲突处理会因此莫名其妙地失败,而没人会把原因追到一个门禁头上。这也是 spec 生成物没有 merge driver:两个 PR 各改几行,语义上是集合运算,却每次都打成文本冲突 #4675
合并驱动那个坑的另一个入口:合并进行中跑任何 check,都可能把半合并的树算出的 manifest
写进磁盘 —— build: merge driver for generator-owned spec artifacts (#4675) #4702 的 commit message 恰好用一整段解释了为什么合并驱动故意不在那一刻
重新生成:「a plausible generated file is an invisible error」。
只有这一个是「stale ⇒ 我帮你写了」,而且混在同一个
check:generated汇总里。改法是把这段改成和紧随其后的 authorable-surface ratchet 同构:
--check打印未记录的 keygen:schema提示,然后process.exit(1);非--check照旧写。missing(已发布 schema消失)那条一个字没动 —— 它本来就是对的,新单测把它钉住以防被改坏。
改动面:
packages/spec/scripts/build-schemas.ts(控制流)+ 一个新单测文件 + 一个 changeset。没有碰任何生成物(
authorable-surface.json/api-surface.json/json-schema.manifest.json/spec-changes.json),也没有碰src/下的任何 schema。1. 缺陷复现(修复前的代码,真实输出)
把
json-schema.manifest.json改成陈旧状态(删掉ui/View这一个 key),只跑--check:一条「检查」退出码 0,却把 tracked 文件的内容改了:本地那处修改被无声地覆盖回 HEAD。
issue 里
git stash pop报Your local changes ... would be overwritten是同一件事的另一面(那边的树里 HEAD manifest 本身就与构建结果不同,于是写出来的是第三种内容)。
2. 修复后,同一场景
同样删掉
ui/View再跑--check(隔离沙箱):同一件事在真实 worktree 里再做一遍,用
git status作证(工作区被改没被改,git 说了算):对照修复前:同样的
M会在跑完 check 之后消失(见第 1 节)。3. 非 check 模式行为不变(
gen:schema仍然写)同一份陈旧 manifest,去掉
--check:4.
missing分支(已发布 schema 消失)未被改坏往 manifest 里塞一个任何构建都不会产出的 key:
单测
新增
packages/spec/scripts/build-schemas-check-mode.test.ts,5 条,全部是运行时 e2e:spawn 真正的
build-schemas.ts,断言退出码和文件字节,而不是断言算术(diff 的算术本来就一直是对的,缺陷在副作用和退出码上)。第 5 条是负控制:manifest 最新时
--check必须 exit 0 且不抱怨,否则「永远红」也能骗过前四条。选择运行时 e2e 而不是编译期 pin 是有意的:本包
tsconfig.json的include只有src/**/*且
exclude掉**/*.test.ts,vitest 也不开typecheck—— 也就是 #4642 说的「编译期 pin在本包空转」,
scripts/与测试文件都不在tsc --noEmit的视野里。所以断言必须真的被执行到。为什么用沙箱而不是直接跑真包:脚本所有路径都从自己的
__dirname解析,就地跑会改到仓库里tracked 的
json-schema.manifest.json;而turbo run test期间@objectstack/spec的build(第一步就是gen:schema)可能正在写同一个文件,测试会既有破坏性又 flaky。于是每次运行都在临时目录里进行:复制
scripts/(让__dirname落在沙箱里),软链只读输入src//node_modules//package.json,再放一份json-schema.manifest.json和authorable-surface.json的副本。这样生产代码路径一字未改 —— 没有给门禁加任何 test-only 接缝,因为接缝本身就是「门禁与 CI 实际跑的东西产生差异」的地方。
sabotage 矩阵 —— 每条断言都被证明会红
「加完是绿的」不算证明。11 轮定向破坏,每轮只破坏一件事,贴真实失败行:
Tests 5 passed (5)122,151退出码expected +0 to be 1process.exit(1)122,151退出码204负控制:输出不该抱怨 manifestmissing分支丢掉process.exit(1)168退出码184📒 … updated不见了renamedAway从 staleness 条件里掉出去151退出码128,156字节断言missing分支退出码正确,但仍然写文件171字节断言gen:schema这个补救命令126123,152124,1557/8 两轮是关键:1/2/4 轮都停在退出码断言上(vitest 一条断言失败即中止该用例),
所以单靠它们证明不了「文件没被动过」那条 —— 于是 7/8 轮保留正确的退出码、只多加一次写,
让唯一可能变红的就是字节断言:
第 1 轮(直接
git checkout origin/main -- scripts/build-schemas.ts)是最直白的一轮 ——新单测放到修复前的代码上就是红的:
每轮结束都校验源文件被完整还原(
md5sum与修复版一致),最后工作区只剩本 PR 的三个文件。未单独 sabotage 的少数断言(如两处
not.toContain('📒')、负控制里的字节断言)都是同一用例中已被证明会红的断言之后的冗余护栏,不是独立主张。
changeset 定级:
@objectstack/specpatchpackages/spec的files字段不含scripts/,所以这次改动不进 npm 包、不改任何公开契约(没有 API、schema、authorable key 变化)。定
patch而不是「不写 changeset」,是因为它改变了一个 CI 门禁的成败语义,受影响的是贡献者:
gen:schema⇒check:authorable-surface静默把manifest 写好并 exit 0,CI 绿。
pnpm --filter @objectstack/spec gen:schema,与其余七个生成物完全一致。
check:generated --fix与check:docs(它本来就先跑一遍gen:schema)行为不变;干净 checkout且 manifest 是最新的情况下,门禁照常是绿的(负控制那条单测就是钉这一点的)。
scripts/regen-artifacts.mjs里json-schema.manifest.json的check正是check:authorable-surface—— 这次改动让那条「checkproves currency」从名义上变成事实上成立(在此之前它是靠把文件改成 current 来「证明」current 的)。
门禁(全部实跑)
跑了两轮:一轮在原始 base 上,一轮在合并
origin/main之后(期间 #4651 / PR #4718 与#4463 / PR #4715 落地,
packages/spec在对面动过 —— 正是 AGENTS.md §10 点名要重跑的情况)。两轮都是单锁串行(本机重验证全局互斥,
flock /tmp/os-heavy-verify.lock)。下表是合并后的:pnpm install --frozen-lockfile+pnpm --filter @objectstack/spec buildexit=0pnpm --filter @objectstack/spec check:generatedexit=0— 8/8 全部 up to datepnpm --filter @objectstack/spec check:authorable-surfaceexit=0pnpm --filter @objectstack/spec testexit=0—Test Files 294 passed (294)/Tests 7387 passed (7387)pnpm --filter @objectstack/spec typecheckexit=0pnpm turbo run typecheck --concurrency=2exit=0—Tasks: 122 successful, 122 totalpnpm turbo run test --concurrency=2exit=0—Tasks: 133 successful, 133 total合并后
git diff --name-only origin/main...HEAD恰好是本 PR 的三个文件:跑完整套门禁之后工作区没有任何计划外改动 —— 这本身就是本 PR 的论点。
顺带发现,已单独立单:#4723(本 PR 不修)
"check:docs": "pnpm gen:schema && tsx scripts/build-docs.ts --check"—— 第一步是生成器。所以「检查改工作区」这件事,在
check:generated这条路径上只被本 PR 消除了一半:而且
check:generated里check:authorable-surface在前、check:docs在后且不会因前者失败而停,于是修完之后的组合行为是:先红着报「manifest 陈旧」,再被后一条悄悄写好。这比修之前更
需要解释 —— 但那要动
build-docs.ts对磁盘上json-schema/的依赖,是另一个决定,写在 #4723里等维护者拍板,没有顺手扩面。
顺带说明(未改动)
--check仍然会清掉并重新生成 gitignored 的packages/spec/json-schema/目录。那不是 tracked文件,两个 ratchet 读的都是内存里的
generatedSchemas,与本 issue 无关,故意不动。本 PR 不触碰 issue 里提到的关联单(#4650 / #4666 / #4659 / #4663),它们各自有单。
与并行进行的 #4651 文件零重叠:只改
packages/spec/scripts/build-schemas.ts的控制流,加一个新测试文件和一个 changeset;没有碰
src/、生成物、ADR-0087 注册表、活性账本或严格性台账。Generated by Claude Code