You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
spec-changes.json is stale — the ADR-0087 registries changed without regenerating the manifest.
Run: pnpm --filter @objectstack/spec gen:spec-changes (and commit the result)
原因:spec-changes.json 装的是对象数组(变更记录),不是字符串数组。我那段代码只在「两侧元素全是字符串」时走集合合并,其余情况回退成「取相对 base 变了的那一侧」—— 直接丢掉另一侧的条目。本 PR 因此丢了 main 侧 #3355 的 object-managed-by-system-to-system-data。
Activity
os-zhuang commentedon Aug 2, 2026
立单时我把本单说成「立刻消除返工」。这话对本地
git merge origin/main成立,对 GitHub 合并队列不成立,差别很重要。.gitattributes里的merge=os-regen只是给合并策略起个名字;真正的驱动要在git config merge.os-regen.driver里定义,那是本地配置,不随仓库提交。GitHub 的服务端合并(合并队列、Update branch、squash)不会、也无法执行自定义 merge driver。实测佐证(2026-08-02):PR #4670 在解决完与 #4664 的冲突、全绿入队后,仍被队列以
MERGE_CONFLICT弹出 —— 因为期间 main 又前进了 #4668、#4660 两个 spec PR。merge driver 即使已落地也拦不住这一次。修正后的定位
api-surface.json、spec-changes.json、docs/protocol-upgrade-guide.md,加上gen:docs产物)属投影类;移出后只剩authorable-surface.json与两个手写登记表。所以两单的优先级与我立单时写的相反:#4676 才是对队列有效的那个,#4675 是对本地开发体验有效的那个。已同步更新 #4676。
(另一个与队列相关的现实解法是降低并发 —— v17 收尾期 main 上 spec PR 密度极高,最近 8 个提交里 6 个动 spec。这属于流程协调,不属于本单。)
Generated by Claude Code
os-zhuang commentedon Aug 2, 2026
已验证的先行实现(2026-08-02,用它解决了 PR #4670 的冲突)
维护者问是否现在同步做本单。建议排到 v17 之后,理由见上一条更正(driver 是客户端机制,救不了合并队列)+ 两点:本单自身也要挤同一个队列;且 driver 需要每个 clone 单独
git config merge.<name>.driver,而 agent 都在新建 worktree 里工作,等于给派发流程加一步配置。但解法本身已经跑通了,留给实现者省一轮摸索。
三路集合合并
这批生成物是排序集合,合并语义是集合运算而非文本:
git show :1:/:2:/:3:拿到 base/ours/theirs 后按此规则合并即可。核心逻辑:对象节点递归、标量取「相对 base 变了的那一侧」。
实测结果(PR #4670 合并
origin/main):7 个冲突文件里api-surface.json/authorable-surface.json/dual-source-exports.baseline.jsongit 自己就合干净了,spec-changes.json用上述集合合并秒级解决,结果正确 ——dual-source-exports.baseline.json从 base 的 22 条正确收敛到 19(本 PR 删RetryPolicy×2,main 的 #4662 删ActivationEventSchema×1)。两个坑
check:generated比对字节,凭空加/减一个\n会让 CI 因纯装饰性原因变红。从:2:的原始字节判断即可。conversions/registry.ts/migrations/registry.ts是代码,且双方新增的条目会共享结构行(});、fixture: {),union 策略会把两个条目的片段拼碎。feat(spec)!: converge RetryPolicy onto one declaration (#4661, C8) #4670 里可行的办法是:以 main 版为底,把diff base ours的 hunk 用patch -F3打上去,然后逐一验证注册落在正确的聚合数组里(fuzz 匹配可能落错位置 —— 我实测两个 hunk 都用了 fuzz 3,验证后确认正确)。所以 driver 的覆盖范围应当只含生成物,
.gitattributes里不要把两个 registry 纳进来。生成器不可用时的降级
driver 跑不了生成器(裸 CI、rebase 中间态、worktree 未
pnpm install)时,集合合并不需要任何依赖,只要 node,可以作为主路径而不只是降级 —— 再由 CI 的check:generated验证结果确实等于重新生成。#4670 走的就是这条路。Generated by Claude Code
os-zhuang commentedon Aug 2, 2026
上一条我把三路集合合并当作先行实现贴了出来。那段代码有缺陷,实测把 CI 打红了,在有人照着实现之前必须说清。
发生了什么
PR #4670 用它解决
spec-changes.json的冲突后,CI 报:原因:
spec-changes.json装的是对象数组(变更记录),不是字符串数组。我那段代码只在「两侧元素全是字符串」时走集合合并,其余情况回退成「取相对 base 变了的那一侧」—— 直接丢掉另一侧的条目。本 PR 因此丢了 main 侧 #3355 的object-managed-by-system-to-system-data。修正后的适用边界
dual-source-exports.baseline.jsonauthorable-surface.jsondocs-import-surface.baseline.jsonapi-surface.jsonspec-changes.jsondocs/protocol-upgrade-guide.md对象数组要做正确的集合合并,得先定义元素身份(如
id),再按 id 做三路合并。 但spec-changes.json是 ADR-0087 登记表的投影 —— 与其猜它的合并语义,不如直接从已合并好的登记表重算,那才是唯一正确的定义。给实现者的结论
driver 不要试图用通用集合合并覆盖所有生成物。分两档:
spec-changes.json、upgrade guide、reference 文档、api-surface.json)—— 必须跑生成器。好消息是成本比想象低:本次pnpm install --filter @objectstack/spec...走缓存 2.5 秒,gen:spec-changes+gen:upgrade-guide秒级完成。也就是说本单原本设想的「driver 调生成器」是对的,我上一条提出的「集合合并作为主路径」只适用于第 1 档。
附带教训
「让 CI 验」这条快路径确实抓住了错误 —— 但它抓住的是一个本可以在本地十秒内避免的错误(跑一次
check:spec-changes即可)。跳过本地验证换来的时间,被一次 CI 往返和一次队列重排赔了回去。若 driver 落地,它应当在解决完冲突后自动跑对应的check:*,而不是把验证推给 CI。Generated by Claude Code
os-zhuang commentedon Aug 2, 2026
第四个样本,以及一条会改变实现方式的失败模式
今晚 #4687 与 main(
742cebb)又撞了一次,按本单的分类拆开完全吻合:packages/spec/spec-changes.jsongen:spec-changesdocs/protocol-upgrade-guide.mdgen:upgrade-guidepackages/spec/src/migrations/registry.ts前两个是生成物,我实际做的就是
git checkout掉冲突副本再跑生成器——和本单的处方逐字一致。第三个是手写登记表,正如实现注意 2 所说需要人看,但也确实又一次是「保留两边」。driver 会把 3 个冲突降到 1 个,且消掉的恰好是贵的那两个(重新生成 + 重跑门禁)。三次冲突里仍然没有一次是真语义冲突。实现注意 3 需要加强:不可用是安全的,陈旧才是危险的
原文的顾虑是「driver 在裸 CI 或 rebase 中间态可能不可用」。不可用其实是安全的失败——它会报错,人能看见。真正危险的是可用但 dist 陈旧。
api-surface.json的生成器readsDist。本会话实测过一次:在不完整的dist上跑gen:api-surface,它静默抹掉了一个与本次改动完全无关的./studio的ActivationEvent (type)导出,并顺手往docs-import-surface.baseline.json里为这个缺口加了一条棘轮豁免——正是该工具自己警告过的「棘轮悄悄停止棘轮」。没有任何报错;是把生成物与main逐文件对比才发现的。放到 merge driver 的语境里,这个性质会翻转本单的收益:
一个对
api-surface.json乐观重算的 driver,会在 dist 陈旧时把「人能看见的冲突」换成「人看不见的错误产物」,而且它落在 diff 里长得完全合理。那比现状更糟。建议
api-surface.json这一条(以及任何readsDist的生成器)应当 fail-loud 而非 best-effort:dist比src旧 / 缺失时拒绝生成,保留冲突标记并提示手工跑生成器。不读 dist 的那几个(
gen:schema、gen:spec-changes、gen:upgrade-guide、gen:docs)没有这个问题,可以直接按原方案重算——今晚我手工走的正是这条路径,结果与门禁完全一致(17 项check:*全绿)。与 #4663 的互补关系不变:那边要求生成物与重算结果逐字节相同,本单保证冲突时必定走重算——前提是重算本身可信,这也正是上面那条要求存在的理由。
Generated by Claude Code
build: merge driver for generator-owned spec artifacts (objectstack-a…
fix(spec): --check must not write json-schema.manifest.json (objectst…
Merge origin/main into claude/issue-5553-file-description-rendering
chore(docs): 合入 main 后整体重跑 gen:schema && gen:docs (#5553, #6136)
Merge origin/main into claude/issue-5553-file-description-rendering
Merge origin/main into claude/issue-5553-file-description-rendering