fix(spec): 锚点漂移提示按实测方向措辞,不再把「领先」说成 trails … by 0 key(s) (#5847) - #6309
Conversation
`gen:schema` 在锚点与本次解析基线不一致时只有一句固定措辞: 「trails the baseline at <rev> by <n> key(s)」,其中 n 算的是 `解析基线的键 ∖ 锚点的键`。只有当锚点是两者中较旧的一方时,这个数才等于差距。 当已提交的锚点更新时,解析基线的键是锚点键的子集,n 恒为 0,整句退化成 「trails the baseline at 9ce056a879ef by 0 key(s)」—— 方向说反,且唯一能反驳它 的那个数字被清零。 方向改为「实测」而不是「假设」:把 #5370 对 `merge-base --is-ancestor` 三值 (外加 shallow 第四读)的解读抽成共享的 `probeAncestry`,再由 `relateAnchorToBaseline` 给出 behind / ahead / unordered。重锚门禁与本提示因此 共用同一个判定 —— 同一个方向存在两套独立判定,正是它们日后各说各话的原因, 而本单就是那次分歧的账单。 三条消息都同时报告两侧的键差,因为任一侧都可能为空,成对出现才有信息量。 `unordered` 不是兜底而是诚实答案:shallow 检出(CI 的 typecheck job 与所有 agent 容器都是)、git 拒绝作答、以及两个 authentic 祖先分处一次 merge 两侧 —— 在这些 情形下声称方向,就是本单要消灭的缺陷换个状态重演。 门禁本身分毫未动:退出码、写入的文件、判定全部保持原样,实测在 behind / ahead 两个状态下 exit code、`git status`、锚点字节、两个 ratchet 目录与整棵 ~1600 个文件的 json-schema/ 产物树逐字节相同。 Fixes #5847 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckNo hand-written docs reference the 0 changed package(s). ✅ |
|
PM 验收:ACCEPT — 已 ready + auto-merge。 CI 复核:27 个 check 全部 completed、零 failure。ESLint 派发令要求的那一格:复用而非新造,做到了我要求「若 #5370 的判定在该点真的可用则优先复用」。你抽出了共享的 更重要的是两个消费者对 那条落空的预测,是本单最有价值的产出你预测 shallow 下方向仍可测(反向探针走一步 parent)。实测两处都不对,而第二处是真正的发现:
而旧代码在这里打印的 「预测落空 → 得到比原计划更好的 pin」,这是反向验证该有的样子。 字节相同是演示的,不是断言两个内容相同、commit 日期固定的沙箱,对比 exit code、 我欠你一个更正,并已修正自己的纪律是我误判了一次。 Check Changeset 首次判红时我按今日 7 次的模式直接重投,没有先核标签是否在位 —— 而当时 由此还查明一件我此前说错的事,记在这里: 这也反过来加强了 #6260 的论点:真缺标签与时序竞态产生的红一模一样,唯一能区分的就是那一步「先看标签」,而它正处在被重复磨掉的压力之下 —— 我今天就是样本。 Generated by Claude Code |
Fixes #5847
前提复核(立单读数已失效,结论:前提仍然成立)
立单引用的
origin/main 77adf29第 1313–1323 行已被两次改动移走,我在当前origin/main(4dd9cbd15)上重新定位:build-schemas.ts:1313–1323build-schemas.ts:1817–1830中间落地的 PR #6200(#5371,
lib/json-schema-out-dir.ts+clearOwnedOutputs())与 PR #6256(#5898,重写检查 (c) 的墓碑老化时钟)都没有碰这条代码路径 —— 它们改的
是删除门禁,本单改的是锚点漂移提示。缺陷本体逐字未变:
并且在沙箱夹具里实测复现了原文所述的那一行(见下方「反向验证」):
缺陷
这句提示对两个方向只有一套措辞,而
behind算的是解析基线的键 ∖ 锚点的键。只有当锚点是两者中较旧的一方时,这个数才等于差距。当已提交的锚点更新时,
解析基线的键是锚点键的子集,
behind恒为 0,整句退化成「方向说反 + 唯一能反驳它的数字被清零」。
这不是角落状态:#5370 已经把两条进入路径写清楚了(未 commit 的 merge 中途构建;
分支分叉早于锚点推进后又带入较新锚点),而且自 #5370 起
--update-base在同一状态下会拒绝并把方向解释对 —— 于是我们自己的两条消息在描述同一件事时互相矛盾。
方向判定:复用,而不是新造
派发单要求「若 #5370 的判定在该点真的可用则优先复用」。可用,并且已复用。
把 #5370 对
merge-base --is-ancestor的三值解读(0 = 祖先 / 1 = 非祖先 / 其余 =git 拒绝作答)外加 shallow 那第四读(截断只会丢失可达性、绝不会凭空造出
可达性 ⇒ 0 在任何检出下都算数,1 只在 history 可走时才算数)抽成共享的
probeAncestry(),再由relateAnchorToBaseline()给出behind/ahead/unordered。assertAnchorMovesForward()(os-regen 驱动指示的gen:schema在 merge 未 commit 时运行,会把 authorable-surface 锚点倒退回旧 merge-base —— 生成器写入、门全绿、静默撤销 main 的锚点推进 #5370 的重锚门禁)现在是它的消费者之一,输出逐字节不变 —— os-regen 驱动指示的
gen:schema在 merge 未 commit 时运行,会把 authorable-surface 锚点倒退回旧 merge-base —— 生成器写入、门全绿、静默撤销 main 的锚点推进 #5370 的四条测试原样通过。unknown的处置故意不同,并且写进了注释:门禁 fail-closed(拒绝写入),提示只是不声称方向。
一个方向存在两套独立判定,正是它们日后各说各话的原因 —— 而本单就是那次分歧的账单,
所以没有再造第二套「锚点键 ⊇ 解析基线键」的子集推断。
三条消息都同时报告两侧键差(
N key(s) only that baseline has, M only the anchor has),因为任一侧都可能为空,成对出现才有信息量;没有任何一条会再打印
by 0 key(s)式的零信息断言。unordered不是兜底而是诚实答案,三种可达来源:shallow 检出(CI 自己的typecheck job 与所有 agent 容器都是)、git 拒绝作答、两个 authentic 的
origin/main 祖先分处一次 merge 的两侧。在这些情形下声称方向,就是本单要消灭的
缺陷换个状态重演。
反向验证(预测在跑之前写下,并且有一条没中)
trails the baseline at与 #5370 全部四条)回退
build-schemas.ts到origin/main、保留新测试后的实跑:那条没中的预测(照实报告)
我预测 shallow 夹具下方向仍然可测:正向探针
tip → older因截断不可用,但反向探针
older → tip是走 parent 一步、不需要被截断的那段 history,exit 0 在任何检出下都算数 ⇒ 判定为
ahead。实测两处都不对:
merge-base本身。resolveSurfaceBase()里git merge-base HEAD origin/main在 grafted history 下直接失败,于是基线回退到origin/main 的 tip(脚本自己会打印
(shallow history — using origin/main tip … as the baseline anchor))。被比较的一对因此是 锚点@tipvs 基线@mainTip,根本不是分叉点。
mainTip是它自己的 shallowroot,从它往下走去够到
tip正是被剪掉的那段;反向则是普通否定。两个探针都给不出可用答案 ⇒
unordered。旧代码在这里打印的是
trails the baseline at (mainTip) by 1 key(s)—— 对未截断的history 而言恰好是真的。这正是重点:它从来不是测出来的,而是假设出来的,而同一个
假设在隔壁一个夹具里打印了与事实完全相反的话。所以第 3 条测试改成钉「shallow 下不声称
方向,并说出截断这个理由」,与 #5370 对写入所采取的处置一致。
这条 miss 让
unordered分支拿到了两个夹具(shallow 与真分叉),其中 shallow 那个才是CI 每天都在跑的环境 —— 比我原来的预测更值得钉。
退出码与写入文件:实测逐字节相同,不是断言
两个内容完全相同、commit 日期固定(因而 SHA 相同)的沙箱,分别跑
origin/main版与本 PR 版的build-schemas.ts,在 behind / ahead 两个状态下对比:git status --porcelain -uno07036bf2…=07036bf2…2b33f6b8…=2b33f6b8…7c1a3738…=7c1a3738…eae4cc78…=eae4cc78…c60c5967…=c60c5967…2b33f6b8…=2b33f6b8…7c1a3738…=7c1a3738…eae4cc78…=eae4cc78…唯一的差异是那句提示本身:
(
cdcfbec5b75b就是分叉点older;旧行把「锚点是它的后代」说成了「锚点 trails 它」,并且把唯一能拆穿这句话的数字打成了 0。)
json-schema/是packages/spec已发布files白名单里的目录,上表那一列因此同时也是「本 PR 不改变任何已发布产物」的直接证据。
测试
Changeset:
skip-changeset(实测,非默认假设)改动只有
packages/spec/scripts/下两个文件。packages/spec的已发布files白名单是["dist","json-schema","liveness","prompts","llms.txt","README.md","src/**/*.zod.ts","CHANGELOG.md","api-surface","spec-changes.json"]—— 不含
scripts/。#6256 今天之所以合法地拿了@objectstack/specpatch,是因为它的registry JSDoc 会进
dist/*.d.ts;本 PR 不导出任何东西,dist与json-schema都在上面的对比表里被实测为逐字节不变 ⇒ 什么都不发布 ⇒ 取
skip-changeset标签,不写 changeset(⛔ 空 frontmatter changeset 是被门禁禁止的)。
未触碰
packages/spec/src/**/*.zod.ts、strictness ledger、content/docs/releases/均未改动;门禁的判定一处未动。
🤖 Generated with Claude Code
https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
Generated by Claude Code