从 #4658(双源清账 C6)的路线定价实验里掉出来的范围外发现,**不在 #4658 的 PR 里修**,单独立案。与 #4650 同族(都是 authorable-surface 门禁的自我校验漏洞)。 ## 事实 `packages/spec/scripts/build-schemas.ts` 检查 (b)(「live → retired 必须有登记的迁移」)这样匹配: ```ts const surfaces = registeredRetirementSurfaces().flatMap((s) => s.split(' / ')); const unregistered = newlyRetired.filter( (k) => !surfaces.some((s) => s.endsWith('.' + k.split(':')[1])), ); ``` `k` 形如 `automation/Event:type`,`k.split(':')[1]` 只取**叶名** `type`,再拿它跟**全部** major 的所有 conversion surface 做 `endsWith`。匹配完全不看 key 属于哪个 def —— 任何一条无关的 conversion 只要 surface 以同名叶子结尾,就把这个 tombstone 判为「已登记」。 ## 实测证据 在 #4658 的定价实验里,我把 `src/automation/state-machine.zod.ts` 的 `EventSchema` 两个 key 同时改成 `retiredKey(...)`,**没有**新增任何 conversion。`pnpm check:authorable-surface` 的输出: ``` ❌ 1 key(s) were tombstoned with no registered migration: - automation/Event:schema ``` 只报了 `schema`。`automation/Event:type` **静默通过** —— 因为 `CONVERSIONS_BY_MAJOR[11]` 里 `flowNodeHttpRename` 的 `surface: 'flow.node.type'` 以 `.type` 结尾。两者毫无关系:一个是 flow 节点的 `type`,一个是 automation 状态机事件的 `type`。 ## 为什么要紧 检查 (b) 的整个目的是保证退休进入 `spec-changes.json` / upgrade guide / `os migrate meta`(ADR-0087 D4)。叶名匹配让这个保证对**常见叶名**整体失效:`type`、`name`、`config`、`filter`、`schema` 之外的 `objectName`、`flowName` 等 surface 已在册,任何以它们结尾的 key 都能零迁移文档退休,门禁全绿,消费者只能靠失败才知道。 这与 #4650 是同一类问题:门禁的证据链自己不设防。#4650 是「删基线行没人校验」,本单是「登记检查可被无关条目满足」。 ## 建议处置(需维护者裁决,故不自行动手) 把匹配从叶名收紧到**定位到 def 的路径**。两个方向: 1. **surface 里带上 def**。让 conversion 的 `surface` 与 `${defKey}:${name}` 可判定地对应(或加一个显式的 `retires: ['automation/Event:type']` 字段),检查 (b) 改成精确集合匹配。最严格,但要回填现有 conversion。 2. **保留 surface 的散文形态,另加一个显式登记表**。新增 `RETIRED_KEYS_BY_MAJOR: Record<number, string[]>`,值就是 `${defKey}:${name}`,检查 (b) 只认这张表。改动小,且「declared = enforced」。 倾向 2:它让登记这件事从「碰巧字符串对上」变成作者必须写下确切的 key,正是 AI 生成的元数据最容易蒙混的那一环。 关联:#4650(基线手编漏洞)、#4658(发现现场)、#4535、ADR-0087、ADR-0104
Activity
Merge origin/main into claude/issue-4661-retry-policy-dual-source
os-zhuang commentedon Aug 5, 2026
补一条现场记录:叶名词表刚刚又宽了一格,新增
description。PR #5509(#4827,ADR-0087 D2 条目
page-header-subtitle-alias)登记的 surface 是:按本单描述的匹配逻辑,
registeredRetirementSurfaces()把它拆进候选集之后,检查 (b) 里任何叶名为description的 key(形如ui/ElementTextInputProps:description、ui/PageProps:description等)一旦被 tombstone,都会endsWith('.description')命中这条完全无关的 page-header conversion,从而被判为「已登记迁移」、零迁移文档静默通过。和本单已记录的
.type是同一个机制,只是description在 authorable surface 上的分布比type更广 —— 它是各类组件/字段的通用描述键,所以这一格拉宽的实际暴露面不小。PR #5509 没有绕过这个问题:它就是要登记 page-header 上的这个键,surface 写成别的形状才是不诚实。记在这里只是让本单的处置有一个更新的定价输入 —— 按本单建议 2(显式
RETIRED_KEYS_BY_MAJOR,值为确切的${defKey}:${name})收口后,这类偶然命中会一并消失。Generated by Claude Code
baozhoutao commentedon Aug 6, 2026
认领:PM 循环第 4 轮(spec-tooling 车道,C 包 #5163)
会话:
session_01559M8FVm6W6vDLABL3jvdW分支:
claude/issue-4659-retired-keys-registryWorktree:
objectstack-issue-4659域:
domain:spec-tooling文件面:
packages/spec/scripts/build-schemas.ts(检查 (b) 的匹配逻辑)、packages/spec/src/migrations/registry.ts(新增显式登记表,非 zod 语义面,#5337 已有本车道先例)、相应测试、api-surface 等生成物整体重生成(新增导出)、.changeset/*.md。⛔ 不碰packages/spec/src/**/*.zod.ts与 strictness 台账。(越界即停,报告说明)PM 裁决:走正文方向 2(显式
RETIRED_KEYS_BY_MAJOR,值为确切${defKey}:${name},检查 (b) 只认此表)—— 三轴中「防 AI 蒙混」与「declared = enforced」都指向让作者写下确切 key 而非碰巧字符串对上;评论区新证据(description叶名把暴露面又拉宽一格)加重同一方向。串行前置:#5807/#5851 已 MERGED,build-schemas.ts让空;#5744(spec 座位,blocked 未认领)文件面为 build-openapi.ts,无交集。Generated by Claude Code
baozhoutao commentedon Aug 6, 2026
验收:ACCEPT → PR #5902(转 ready 入队)
裁决(方向 2)按样落地,且暴露面测量把本单从「一个坑」升格为「整面墙」:现有 97 个 [RETIRED] 键全部存在叶名巧合命中 —— 检查 (b) 对账面上每一个墓碑都是可满足的。复核确认:
RETIRED_KEYS_BY_MAJOR纯追加落位(1976-2043 行,零既有行触碰)、精确${defKey}:${name}集合匹配、处方可整行复制粘贴(点名 major 与文件);.claude/skills/spec-property-retirement/SKILL.md)§3 把本缺陷当 caveat 记着 —— 一并更正,防下一次退役照旧文档踩回去;虽超出申报文件面,采纳。CI 24 项零红。必答项(#4725 定价):略变简单(登记证明结构有了在树模板 + 错误路径已在 TSDoc 封死),reachability 半边与 manifest 同删缺陷不受影响,原价保留;注意 per-KEY 表答不了 per-def 问题,#4725 需要姊妹表或纯 reachability。
范围外发现 #5898 已核实(检查 (c) 同款叶名匹配 + Math.min 取最早 major → 基线行会被提前放行,
data/Index:type的时钟被 protocol 11 无关条目启动 —— 实测非推理;97 条回填是独立判断,不做 rider 的克制正确)。Generated by Claude Code