Skip to content

check:authorable-surface 在 --check 模式下仍会写 json-schema.manifest.json —— 一个「检查」在改工作区 #4711

Description

@os-zhuang

在 #4703(C12,PR #4710)做承接表 sabotage 验证时撞到的,与该单无关,按 Prime Directive #10 单独记录。

现象

pnpm --filter @objectstack/spec check:authorable-surface 是 build-schemas.ts --check,名字和 check:generated 里的定位都是「只检查、不改」。但它会写 packages/spec/json-schema.manifest.json。

实测:我把 authorable-surface.json / json-schema.manifest.json 暂存(git stash push)到改名前的状态,只跑了 --check,随后 git stash pop 直接失败:

error: Your local changes to the following files would be overwritten by merge:
	packages/spec/json-schema.manifest.json
Please commit your changes or stash them before you merge.

原因

packages/spec/scripts/build-schemas.ts 的 manifest ratchet 段落没有 CHECK 判别:

const added = [...generatedKeys].filter((key) => !(manifest?.schemas ?? []).includes(key));
const renamedAway = (manifest?.schemas ?? []).filter((key) => key in RENAMED_DEFS);
if (!manifest || added.length > 0 || renamedAway.length > 0) {
  const updated: SchemaManifest = { /* … */ schemas: [...generatedKeys].sort() };
  fs.writeFileSync(MANIFEST_PATH, JSON.stringify(updated, null, 2) + '\n');   // ← 无条件写
  console.log(`\n📒 json-schema.manifest.json …  — commit it.`);
}

对比紧随其后的 authorable-surface 段落,那里是分开处理的,正是本 issue 期望的形状:

if (surfaceChanged && CHECK)  { /* 报错 + process.exit(1) */ }
if (surfaceChanged && !CHECK) { /* 写文件 */ }

即:missing(已发布 schema 消失)那条会 process.exit(1),是真检查;但新增/改名导致的 manifest 变更在 check 模式下被静默写掉,而不是报「stale,请跑 gen:schema」。

为什么值得修,而不只是洁癖

  1. 「检查」不该有副作用。 check:generated 会顺序跑这一条,开发者跑一次门禁就得到一个未预期的 tracked 文件改动;在脏工作区里 git stash / git worktree 一类操作会莫名其妙地失败(上面那段就是)。
  2. 它让 manifest 的 additions 分支永远无法在 CI 里红。 其它 7 个生成物都是「stale 就红,让人跑 gen」;这一个是「stale 就自己写」。两种语义混在同一个 check:generated 汇总里。
  3. 和刚落地的 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」。这里是同一个坑的另一个入口。

建议

把 manifest 段落改成和 authorable-surface 同构:

  • CHECK 时:若 added.length > 0 || renamedAway.length > 0,打印差异 + 提示 pnpm --filter @objectstack/spec gen:schema,process.exit(1);
  • 非 CHECK 时:照旧写。

missing 那条(已发布 schema 消失)的行为不变,它已经是对的。

影响面

低——CI 是干净 checkout,check:docs 本身又会先跑一遍 gen:schema,所以线上不会漏。主要是本地开发/agent 并行工作时的困惑成本,以及上面第 2、3 条的语义漏洞。

关联:#4684 / PR #4695(RENAMED_DEFS 与 renamedAway 分支的来源)、#4675 / PR #4702(生成物合并驱动)、#4703 / PR #4710(撞到本问题的上下文)、#4203 / #4232(check:generated 分类不一致的历史)。

Activity

  1. self-assigned this
    on Aug 2, 2026
  2. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    🔒 认领 — 开「门禁道」并行推进

    • 会话:session_0176qgxgCXTJCUv4YFLtusP9
    • 分支:claude/issue-4711-check-mode-manifest-write

    为什么这一单可以和 #4651 并行

    本轮之前所有 spec 单都是严格串行的,原因是它们都重新生成同一批产物(authorable-surface.json / api-surface.json / json-schema.manifest.json / spec-changes.json + ADR-0087 注册表)—— 2026-08-02 当天为此返工三次,其中一次 PR 已入队仍被合并队列以 MERGE_CONFLICT 弹出。

    但那是生成物的冲突,不是「一次只能派一单」的规矩。本单只改 packages/spec/scripts/build-schemas.ts 里 manifest ratchet 那一段的控制流,不改任何生成物内容(修好之后 --check 反而不再写文件)。与同时在跑的 #4651(改 src/ui/app.zod.ts + authorable-surface.json + ADR-0087 注册表 + 两本账)零文件重叠。

    于是队列拆成两条道:

    道 内容
    A · 契约道 #4651 → C14 #4691 → ADR-0049 批次(#2902 / #3715 / #3207 / #4579 / #2991)
    B · 门禁道 #4711(本单) → #4650

    #4666 不进 B 道 —— 它要让门禁看见默认值/约束变更,多半要往生成物里添记录,那会和 A 道正面撞,留在 A 道的串行里。

    ⚠️ 仍有一个物理上限:机器上的重验证是全局互斥的(flock /tmp/os-heavy-verify.lock),两个 agent 同时跑全仓测试会在这把锁上排队。所以两条道是有效并行的上限,再多没意义。


    Generated by Claude Code

  3. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    ✅ ACCEPT — PR #4724 已转正并挂 auto-merge

    对着 GitHub 实际 diff 复核(不采信自述),全部通过:

    要求 实测
    改动面仅 3 文件,零生成物、零 src/ schema ✓ build-schemas.ts + 新单测 + changeset,git diff --stat 为证
    与 authorable-surface 段同构 ✓ manifestChanged && CHECK → 报错 + process.exit(1)、不写;&& !CHECK → 照旧写
    missing 分支未改坏 ✓ 一字未动 + 新单测钉住(第 4 轮 sabotage 证明拆掉 exit 会红)
    修复前复现 / 修复后对照 ✓ md5 + git status --porcelain 前后对照,真实输出
    单测形态 ✓ 运行时 e2e(spawn 真脚本,断言退出码 + 文件字节),沙箱隔离避免与并行 build 抢写 —— 生产代码零 test-only 接缝
    sabotage ✓ 11 轮矩阵,每轮单点破坏。7/8 两轮尤其到位:退出码保持正确、只多一次写,逼出字节断言独立变红 —— 堵住了「断言中止后文件断言从未被执行」的盲区
    changeset ✓ patch,定级理由诚实(scripts/ 不进 npm 包,但 CI 门禁成败语义变了,受影响者是贡献者)
    门禁 ✓ 合并 post-#4718/#4715 的 main 后重跑两轮全套(单锁串行),8/8 生成物 up to date,全仓 122 typecheck / 133 test 绿

    越界发现处理得当

    #4723(check:docs 第一步就是 gen:schema,「检查改工作区」在 check:generated 路径上只被消除了一半,且修完后组合行为变成「前一条红着报陈旧、后一条悄悄写好」)—— 单独立单等维护者拍板,没有顺手扩面。正确。


    B 道下一单 #4650 暂缓派发:它要动 build-schemas.ts 的基线检查逻辑,与本 PR 同文件 —— 等 #4724 落地后再派,避免自家道内冲突。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions