fix(spec): --update-base 只许锚点前移,MERGE 态与倒退一律拒绝 (#5370) - #5851
Merged
Conversation
#5358 把重锚收窄成显式模式,回答了「何时可写」,没有回答「从哪儿写」——基线 仍是 merge-base(HEAD, origin/main),而它并不总是领先于已提交的锚点。 merge 停在未 commit 时,HEAD 还是合并前的分支 tip,merge-base 因此落在分支的 旧分叉点而不是被合入的 main tip,重锚就把 baseRev 倒退回去。#5312 同步接力实 测:1c3da1f 回退到 5aae790,#5321 刚退役的 109 键整批回到锚点。 倒退后的文件依然 authentic——旧 rev 是真的 origin/main 祖先、键与该 commit 的 surface 逐行一致——所以 verifyCommittedSurfaceBase、check:authorable-surface、 pre-commit 的 os-regen 守卫前后全绿,唯一痕迹是 diff 里一次反向 baseRev 移动, 形状与 #4650 的攻击无异,而它是生成器自己写的。 两条拒绝,都只作用于 --update-base: - MERGE 态:MERGE_HEAD 存在(经 git rev-parse --git-path 解析,linked worktree 下 .git 是文件,手拼路径会让守卫失效)即在生成 1600 份 schema 之前拒绝,并给出 处方——先 commit merge,再重锚。与 regen-artifacts.mjs 的 "Re-anchor after the merge is committed, or not at all" 同一句话的两端。 - 单调性:仅当已提交 baseRev 是新解析 rev 的祖先时才写;键一致仍走原有的 "nothing to re-anchor"。 无法判定祖先关系时同样拒绝而不猜:merge-base --is-ancestor 按 0 / 1 / 其他 (带 fatal 的 128)读成三种答案;shallow 检出下的 1 判为不可用(截断会让 git 对 明明是祖先的 commit 报「不是祖先」),而 0 在任何检出下都成立——截断只会丢失 可达性,不会凭空造出可达性。 gen:schema 与任何构建不受影响:#5358 之后它们本就不写这个文件。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01559M8FVm6W6vDLABL3jvdW
|
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). ✅ |
baozhoutao
marked this pull request as ready for review
August 6, 2026 08:27
baozhoutao
pushed a commit
that referenced
this pull request
Aug 6, 2026
`api/discovery.zod.ts` gained `CapabilityDescriptor` on main (#5851 line), so the index's api/discovery row and both totals moved: 1608 -> 1609. Worth noting what this merge demonstrates. The frontmatter count and the Total row moved TOGETHER, without anyone touching either, because both are `reduce` over the rows they head — the disagreement this page shipped for months (133 in the frontmatter, 169 in the nav table) has no way to recur. And the drift was caught by `check:docs`, which could not see this file at all before #4759. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01559M8FVm6W6vDLABL3jvdW
This was referenced Aug 6, 2026
Closed
This was referenced Aug 6, 2026
Closed
qq9340100
pushed a commit
that referenced
this pull request
Aug 6, 2026
含 #6000(#4723:check:docs 不再以 gen:schema 开头,并在 check-generated 的 GATED 表 里声明 `readsSchemaTree`),S2 的门禁构成改动叠在它之上而不是并排。 冲突一处,packages/spec/scripts/check-generated.ts 的 GATED 表:两侧都改了 `check:docs` 与 `check:api-surface` 两行。取并集 —— 保留 #6000 的 `readsSchemaTree: 'check:authorable-surface'`(它声明的是 check:docs 依赖 check:authorable-surface 先生成 json-schema/ 树,与分片无关),同时保留本分支把 artifact 标签从 `api-surface.json` 改成 `api-surface/`。 生成物按 os-regen 纪律处理:merge 提交先落地(MERGE 态跑 gen:schema 会被 #5851 守卫 拒绝),随后整体重生成并补跑 gen:openapi(#5371)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
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 #5370
前提复核(先证伪,再实现)
按重新定价的要求,先在 origin/main(
77adf29)上核对 #5807(a3a884d)的落地形态,再证明根因仍然存在:--update-base一条路径,gen:schema/构建/check:docs不再碰它;scripts/regen-artifacts.mjs保留gen: 'gen:schema'并写明「Re-anchor after the merge is committed, or not at all」。本单字面复现路径确实消失。git merge停在未 commit),用 fix(spec): 重锚 authorable-surface.base.json 改为显式动作 —— 构建不再顺手推进删除门的锚点 #5807 落地版跑--update-base,实测:锚点从 origin/main 上的
9ce056a(领先)被写回分支的旧分叉点c26ac40,data/Object:label从锚点里消失,退出码 0,无任何门禁报警。前提成立,范围按重新定价执行。改动
两条拒绝,都只作用于
--update-base,gen:schema与任何构建的行为完全不变。1. MERGE 态拒绝(生成之前)
MERGE_HEAD存在即拒绝,位置与既有的--check --update-base互斥拒绝并列,在读取任何 schema 之前退出。路径经
git rev-parse --git-path MERGE_HEAD解析而不是手拼.git/MERGE_HEAD:AGENTS.md §11 要求每个 agent 都在 linked worktree 里干活,而那里.git是文件,真正的 MERGE_HEAD 在主仓.git/worktrees/下的每-worktree 目录里 —— 手拼路径会让这条守卫恰好在仓库要求使用的检出形态下失效。实测本 PR 自己的 worktree:处方与
regen-artifacts.mjs的注释是同一句话的两端:先 commit merge,再重锚。2. 锚点单调性(写入之前)
只有当已提交的 baseRev 是新解析 rev 的祖先时才写;两者相同时仍走原有的 "nothing to re-anchor"。
放在写入点而不是脚本开头,有两个理由:要写入的 rev 到那里才确定;以及要拒绝的是写入而不是这次运行 —— 一次本来就没东西可写的
--update-base不该被一个 rev 比较变成失败。删除门先裁决、锚点后写的既有次序也因此保持不变。祖先关系判不出来时,拒绝而不是猜
merge-base --is-ancestor按三种答案读:0是祖先,1不是祖先,其余(带fatal:的128、超时的null)是 git 拒绝回答。塞进&&/||链里第三种会塌进第二种,错误就变成了裁决(cloud#1116 付过的学费)。这里 fail closed。shallow 检出多出第四种读法,而且是不对称的:截断只会丢失可达性,不会凭空造出可达性,所以
0在任何检出下都成立;而 shallow 下的1什么都不说明 —— #5358 自己的首次 CI 就是被这个假1判红的。因此 shallow 只用来决定否定答案算不算数,绝不用来丢弃一个肯定答案(否则每一次 shallow 下本该合法的重锚都会被诬告成倒退)。这条不是纸面推演:本容器的检出就是 shallow,锚点的
baseRev正躺在.git/shallow里当自己的 grafted root。真实跑一次:代价是明说的:shallow 检出里一次本来合法的重锚也会被拒,提示给出
git fetch --unshallow origin的出路。gen:authorable-surface-base在check-generated.ts里属EXPLICIT_GENERATORS(手动专用,--fix不会跑),CI 无任何作业调用它,所以这条 fail-closed 不影响流水线。测试
在
build-schemas-check-mode.test.ts的#5358块旁新增#5370describe,沿用同一套真 git 沙箱(refs/remotes/origin/main是真 ref)。夹具:三个线性上游 commit,锚点镜像中间那个(authentic、是 origin/main 祖先、合法滞后),HEAD 分叉在最早那个并把 main 的改动逐字节做了一遍 —— 于是 merge 无冲突且不 stage 任何东西,git status保持全空,「这次运行没写东西」就能直接用空 status 断言,没有 merge 噪声要减。四个用例:
git merge --no-commit --no-ff,MERGE_HEAD 实存):拒绝、退出 1、输出不含Generating JSON Schemas(即生成之前)、锚点字节不变、工作树干净;同一夹具下普通构建不被拒(守卫足够窄,驱动的gen:schema处方照常);随后按处方git commit完成 merge 再重锚,写入成功且 baseRev 前移到 mainTip —— 拒绝有可用的出路,不只是一条规矩。.git/shallow截在 mainTip,此时移动其实是前移,断言拒绝理由是截断而不是 BACKWARDS。verifyCommittedSurfaceBase先一步以「NOT an ancestor of origin/main」判红,那是另一个事实、另一条处方。用例把这个次序钉住并注明:单调性守卫要拦的是两个 rev 都 authentic 的那种倒退,这恰好也是别的门看不见它的原因。反向验证(方向先预测,再运行)
预测:1/2/3 转红(#5807 落地版根本没有这两条拒绝,会照写不误),4 保持绿(它钉的是既有次序)。把
build-schemas.ts换回 #5807 落地版、保留新测试后实跑:与预测一致。#5358 的既有 7 个用例(plain build clean / --check clean / 前移重锚 / already-current no-op / offline powerless / 删除门次序等)全程绿。
实跑输出
Changeset
按 #5807 的先例给
@objectstack/spec一个 patch changeset(同一文件类、同一命令的行为变化):.changeset/authorable-surface-anchor-monotonic.md。范围外
resolveSurfaceBase()的常规解析语义、scripts/regen-artifacts.mjs、os-regen 驱动本身、packages/spec/src/**一律未动。唯一相邻改动是把resolveSurfaceBase()里私有的 git runner 提到模块级(cwd/timeout 逐字不变),因为单调性守卫要在脚本另一处用同一个;以及把REANCHOR_COMMAND常量上移到--update-base旁边(MERGE 拒绝要引用它,而那条拒绝跑在原声明位置之前)。trails the baseline at … by 0 key(s)(方向说反且信息为零),已按 observation-class 单独立单 gen:schema 在锚点「领先于」解析基线时打印「trails the baseline … by 0 key(s)」——方向说反,信息为零 #5847。Generated by Claude Code