fix(spec): manifest 删行相对 merge-base 自证 —— 整 schema 消失不再只是纪律 (#4725) - #5971
Merged
Conversation
`json-schema.manifest.json` 的 #2978 ratchet 对删行的全部要求是一句注释 ("remove a key ONLY for a deliberate retirement"),没有任何机器校验。它的 `missing` 集合是「manifest − 本次产出」,而 manifest 是同一个 commit 里可以随手 改的文件:把导出、manifest 行、基线行一起删掉,`missing` 恒空;检查 (c) 又以 「def 已不再产出」为由放行它名下的每一行(那一条出口原话就写着交给 manifest 裁 判);`check:api-surface` 只是新鲜度门。三个门,一句话都不说。 实测(修复前):删掉 src/data/index.ts 一行 `export * from './validation.zod';` —— ObjectSchema 直接从该模块导入 ValidationRuleSchema,所以 7 个 def 依旧从 `object` 元数据根可达 —— gen:schema 与 check:authorable-surface 双双 exit 0, 116 个 authorable key 静默离开契约。 - 新增 manifest deletion gate:相对 merge-base(HEAD, origin/main) 的 manifest 计算「离开已发布集合」的 def,跑在检查 (c) 之前。 - 新增导出 RETIRED_DEFS_BY_MAJOR(per-DEF,精确集合判定,#4659 先例)+ 镜像检查 (登记了一个仍在发布的 def 直接失败)。RENAMED_DEFS 前置豁免。 - 不复用 #4650 的可达性:reachableVia() 从 zodByDefKey 取实例,该表只装本次构建 产出的 def,刚被删的 def 必然答 null(「不可达 ⇒ 放行」)。 - 离线沿用 #5235 姿态:镜像照跑,比较打印说明并跳过。 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 CheckThis PR changes 1 package(s): 111 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
baozhoutao
marked this pull request as ready for review
August 6, 2026 13:42
This was referenced Aug 6, 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 #4725
前提核验:成立,并且实测过
build-schemas.ts自本单立单起被重写了四次(#5807 / #5851 / #5902 + 中间轮),所以先对着当前origin/main逐条核验:manifest在 L424 从MANIFEST_PATH读盘 —— 本 commit 可随手改的那份文件;missing = manifest.schemas − generatedKeys(L447),没有任何一处从 git 取过 manifest;goneDefs出口原话:whole-schema removals are adjudicated by json-schema.manifest.json (#2978) and check:api-surface;build-api-surface.ts的--check是对已提交快照的比对,失败提示是If intentional, run gen:api-surface and commit the updated snapshots—— 纯新鲜度门。没有任何 #4650 时代的删除证明覆盖到这一层。
实测复现(修复前,沙箱里跑真源码,非模型化): 删掉
src/data/index.ts里一行export * from './validation.zod';。ObjectSchema是直接from './validation.zod'导入ValidationRuleSchema的,所以这 7 个 def 依旧从object元数据根可达、依旧解析真实元数据、它们的 116 个 key 依旧被作者书写 —— 消失的只是 JSON Schema、manifest 行和 ratchet 对它们的记录。再按 #2978 注释所允许的 "deliberate retirement" 手删 7 行 manifest、手删 116 行 baseline(不删的话检查 (a) 先红):检查 (c) 打印的正是那句
def no longer emitted by this build; whole-schema removals are adjudicated by json-schema.manifest.json (#2978)—— 交给了一个什么也不说的裁判。改了什么
1. manifest deletion gate(
scripts/build-schemas.ts)按 #4650 的同一结构重新锚定:相对 merge-base(HEAD, origin/main) 的 manifest —— 本 commit 改不动的那一份 —— 计算「离开已发布集合」的 def(
baseSchemas里、本次构建不产出、且不在RENAMED_DEFS里),每一个都必须有登记,否则红。比较的右侧刻意是
generatedKeys(本次真正产出的集合)而不是树里的 manifest 文件:文件正是 PR 能改的那一半,改它就是本单的旁路。它跑在检查 (c) 之前 —— 这一步是让 (c) 那句「交给 manifest 裁判」从循环变成真裁决的关键;(c) 的措辞也同步更新,不再声称
check:api-surface参与裁判(它不参与)。2.
RETIRED_DEFS_BY_MAJOR(新导出,src/migrations/registry.ts)RETIRED_KEYS_BY_MAJOR的上一层同胞,值是确切的${category}/${SchemaName}('identity/Session'),写法就是 manifest 里的写法。沿用 #4659 的精确集合判定:不取叶名、不按前缀、不从相邻条目辐射。失败时直接打印要粘贴的那几行和该进哪个 major。配套镜像检查(#5902 的 (b2) 同形):表里登记了一个本次构建仍在发布的 def —— 一次没有任何东西消费的登记 —— 直接失败。没有它,真正的删除可以在几个月后别人的 PR 里落地,而门禁早已被满足、当时没有任何人写下任何东西。镜像不需要 git,任何环境都跑。
3. manifest 自身的 description
它此前写的
remove a key ONLY for a deliberate retirement正是 issue 引用的那句「唯一的要求、且没有任何机器校验」。现在它指向这道门和这张表,并纳入陈旧判定(descriptionStale),所以不会再和代码漂移 —— 打开这个文件准备删行的人读到的,就是当前的程序。关键判断:为什么是「声明」而不是「可达性」
派单给了两条路,并要求给证据。issue 建议的 (a) 复用可达性在本层用不了,而且是静默地用不了:
reachableVia(defKey)的第一行是const schema = zodByDefKey.get(defKey); if (!schema) return null;,而zodByDefKey只在产出循环里.set()—— 只装本次构建产出的 def。一个刚刚不再被产出的 def 必然答null,而null在 #4650 的语义里就是「不可达 ⇒ 放行」。也就是说:直接复用可达性,会对这道门要拦的每一类删除开绿灯,而且是安静地开。加宽 BFS 救不了这一点:def 已经从源码里没了,没有图可以走。要在本层拿到「删除前是否可达」,只能引入一个新的已提交投影(把可达性写进 manifest,或另立一个 root-set 快照)—— 那是更大的形状变更,且每次 root 图变动都会产生噪声 diff。所以本层唯一诚实的证据是声明,沿用 #5902 为 key 层定下的先例。
派单要求「measure current main's history for real whole-def removals」:近四天
main上整-def 删除约 15 次(#4988/#5321 一次删 32 行、#4936/#5065 删 13 行、#4739/#4752 删 6 行、#4834/#4878 删 5 行……),其中既有可达形状也有仅声明形状。也就是说纯 reachability 这条路不仅静默失效,业务上也不成立:这类删除是常态,必须有一条能走的路,而这条路必须留下记录。每次一行、门禁把那行原样打给你,是这个记录最小的形态。为什么这不是「把手改从一个文件搬到另一个文件」: 真正的牙齿是 merge-base 锚定 —— 今天这件事根本没有被检测。检测之后,它从「一个 1613 行生成文件里少了一行」(review 里不可见)变成「migrations 登记表里一条具名退休 + 一个 major」(review 里就是它本身),再加上镜像检查堵掉预登记。这与 #5902 为 key 层做的交易完全同形。
离线语义:沿用 #5235,不新造第三种
verifyCommittedSurfaceBase拿不到它要校验的 commit 时的做法一致。那些环境是已合并的不可变树(镜像构建阶段、离网、fork、历史 tag 重放),「这个 PR 相对 main 删了什么」在那里不是一个存在的问题。跳过不会让任何构建失败,所以不需要为它再加一个 in-tree 锚点(spec 的 #4650 删除闸门在「按 SHA 钉住的消费者构建」里无法锚定 origin/main,硬失败 —— cloud 的镜像构建与 pin bump 全线卡死 #5235 给 surface 加锚点是因为那道门在离线环境失败,不是因为它需要离线覆盖)。authorable-surface.base.json未出现在本 PR —— 逐文件核过git status。测试
新增第三个沙箱(
build-schemas-check-mode.test.ts),原因是结构性的:门读的是merge-base 的 manifest,所以json-schema.manifest.json必须是一个被 git 跟踪、且提交内容与工作区不同的文件 —— 第一个沙箱只提交 surface,在那里跟踪 manifest 会让每次seedManifest()弄脏 #5358 断言为干净的树;而登记表要逐用例不同,需要复制而非 symlinksrc/。生产代码路径逐字节不变,只有 fixture 数据。9 条新用例:
RENAMED_DEFS源 def 从 base manifest 消失 → 绿carry their own proof/baseline deletion(s) since反向验证(方向先判后跑,方向如预测:修复前绿 → 修复后红)。 同一套 harness、同一个 fixture,只换
scripts/的版本:本地全量:
api-surface.json因新增导出整体重生成,diff 是单行+ "RETIRED_DEFS_BY_MAJOR (const)"。对 #4723 的定价(必答项)
变简单一点点,方向不变,不会变难,也没有被本单取消。
#4723 想把
gen:schema从check:docs里摘出来,理由是「一个 check 在树脏的时候写生成物」。本单把gen:schema的写面往回收了一格:json-schema.manifest.json的 description 纳入陈旧判定后,写模式的三个写点(manifest / surface / json-schema 目录)各自的触发条件都变得可判定,而新增的这道门在两种模式下行为完全一致、且永不写盘 —— 它是纯验证。所以「把 gen:schema 从 check: 里摘掉,调用方显式跑」这条路径不会因为本单多出新的写副作用要处理。一处需要 #4723 的实现者知道:本门读 merge-base 需要
origin/main可解析,和check:authorable-surface现有的解析路径共用同一次 git 解析(gitResolvedAnchor),没有第二次。若 #4723 把gen:schema拆成独立步骤,这道门必须跟着--check一起走,而不是留在gen:一侧 —— 否则 CI 的验证面会少掉它。🤖 Generated with Claude Code
https://claude.ai/code/session_01559M8FVm6W6vDLABL3jvdW
Generated by Claude Code