Skip to content

fix(spec): 退休登记按确切 key 判定 —— 无关簇的同名叶子不再替 tombstone 背书 (#4659) - #5902

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-4659-retired-keys-registry
Aug 6, 2026
Merged

fix(spec): 退休登记按确切 key 判定 —— 无关簇的同名叶子不再替 tombstone 背书 (#4659)#5902
baozhoutao merged 2 commits into
mainfrom
claude/issue-4659-retired-keys-registry

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #4659

前提复核(先证再改)

issue 正文引的是 08-02 的代码,而 build-schemas.ts 当天被重写过三次(#5807 / #5851)。所以先按机制复核,不按行号:

origin/main 上检查 (b) 的判定逻辑一字未变 —— 取 key 的叶名,和全部 major 的所有 conversion / migration surface 子句做 endsWith:

const surfaces = registeredRetirementSurfaces().flatMap((s) => s.split(' / '));
const unregistered = newlyRetired.filter(
  (k) => !surfaces.some((s) => s.endsWith('.' + k.split(':')[1])),
);

实测复现(改动前,把两个 key 在基线里去掉 [RETIRED] 标记,让门禁看到一次 live → retired 跃迁,不新增任何 conversion):

❌ authorable-surface.json is out of date (2 key(s) not recorded).
     + data/Index:type [RETIRED]
     + security/RowLevelSecurityPolicy:priority [RETIRED]

检查 (b) 一个字都没说,运行一路掉到末尾那个「基线没重新生成」的报告器上。前提成立。

顺带量出了暴露面的真实大小:当前 97 条 [RETIRED] key,97 条的叶名都能在已登记子句里找到匹配 —— 也就是说这道门禁对今天在册的每一个墓碑都是可满足的。

改动

PM 裁决的方向 2,原样落地:

  • 新增导出 RETIRED_KEYS_BY_MAJOR(packages/spec/src/migrations/registry.ts),值是确切的 `${defKey}:${name}` 字符串,与 authorable-surface.json 的写法一致(去掉 [RETIRED] 标记)。

  • 检查 (b) 改为对这张表做精确集合判定 —— 没有 endsWith,没有取叶名,不从相邻 key 辐射。失败信息打印要粘贴的那一行和它该进哪个 major(处方即契约):

    ❌ 2 key(s) were tombstoned with no registered retirement:
         - api/HttpFindQueryParams:distinct
         - data/Index:type
       ...
       1. Declare each retirement by its EXACT key in RETIRED_KEYS_BY_MAJOR
          (packages/spec/src/migrations/registry.ts) — copy these lines in:
    
            'api/HttpFindQueryParams:distinct',
            'data/Index:type',
    
          under `17: [ … ]` (create the major's array if it is the first).
       2. Add a D2 conversion in src/conversions/registry.ts naming the surface ...
    
  • 新增检查 (b2)(dispatch 要求的第 (c) 项裁定):表里登记了一个当前仍然 live 的 key → 失败。理由是它替一次尚未发生的退休提前放行 —— 墓碑真落地那天,检查 (b) 已经被满足,没人再写下任何东西。反过来,登记了一个本次构建已不再产出的 key 不是错误:墓碑满约两个 major 后由检查 (c) 放行其基线行,登记条目留下并从此指向空,这是预期稳态。相反的裁定(「条目必须永远解析得到」)会逼着每次老化都去删掉登记 —— 记录自己把自己删掉。

  • conversion 的 surface 散文一字未动。它面向作者、按作者书写元数据的形状表达(flow.nodes[].outputSchema),本来就无法可判定地映射回 def key;搬走的是机器事实,不是散文。一次退休仍然两样都要写,失败信息两条都点名。

  • 退休 playbook 同步(.claude/skills/spec-property-retirement/SKILL.md §3/§4):原先那条 checklist 写的正是本单的缺陷本身 —— 「surface 必须以裸 key 结尾。匹配是 surfaces.some((s) = > s.endsWith('.' + key)) …… Caveat: 只比最后一段,所以 schema 名从不被检查 —— dashboard.aria 就能满足 ui/FormView:aria。别指望这道门禁做归属判定。」这条留着就是把下一个退休作者送进已经删掉的路径,所以一并改写。

回填语义(dispatch 要求想清楚并机械验证)

不需要回填,已实测。 检查 (b) 只在新的 live → retired 跃迁上触发,而跃迁是相对已提交的 authorable-surface.json 基线算的 —— 更早的墓碑在基线里已经是 [RETIRED],prev.get(k) === false 永远不成立,不会再触发。

机械验证:未改动的树 + 空表,跑真实门禁 check:authorable-surface,以及沙盒里的完整 --check:

  ✓ check:authorable-surface   authorable-surface.json (+ its .base.json anchor) + JSON schemas
=== exit 0 ===

所以这张表读作「在确切-key 门禁下登记的退休」,不是「历史上的全部退休」。这一点写进了 RETIRED_KEYS_BY_MAJOR 的 TSDoc(「Not a backfill of history」),连同一条明确的禁令:不要用叶名匹配 conversion registry 去反推缺失的历史 —— 那正是本单删掉的推断。

registry.ts 的改动行(同日 churn 声明)

packages/spec/src/migrations/registry.ts 是 ADR-0087 活跃工作面。本 PR 对它是纯追加:在文件末尾 MIGRATION_MAJORS 之后新增 1976–2043 行(TSDoc + RETIRED_KEYS_BY_MAJOR),既有行一行未改(git diff 里该文件只有 +,没有 -)。src/migrations/index.ts 只在既有 export 块里多一行名字。

多行拼接字符串会躲开按行 grep,所以反向核对过:registeredRetirementSurfaces 全仓 0 命中(已随本 PR 删除),旧文案 tombstoned with no registered migration 在代码里 0 命中(仅存于 CHANGELOG 与一条历史 changeset 的散文中)。

git merge origin/main(合入 4 个新 commit,含 #5854 / #5861packages/spec 的改动),合并后重建 + 全量复验。

测试

新增 5 个用例,放在既有沙盒文件 build-schemas-check-mode.test.ts,单独一个 describe。

为什么第二个沙盒:这些用例需要门禁读到逐例不同的登记表,而既有沙盒把 src/ 软链到仓库真身,在那里写就是写进被跟踪的 registry。新沙盒改为 cpSync 复制 src/(9.8 MB,约 40 ms),它的 src/migrations/registry.ts 因此是 fixture。门禁本身没有加任何 test-only 接缝 —— 被替换的表是 fixture 数据,和既有沙盒那条伪造的 refs/remotes/origin/main 同性质。

为什么绿色用例仍然 exit 1:live → retired 跃迁的存在,等价于「已提交基线与本次产出不一致」,所以每个 fixture 按构造都会被脚本末尾的「基线未重新生成」报告器判为陈旧。那是另一个失败、另一条处方,断言一律按消息区分,从不只看退出码 —— 这点在测试注释里写明,没有粉饰。

  1. 叶名与无关登记撞车的墓碑不算已登记(spec 双源清账 C6:EventSchema(./automation ≠ ./kernel)—— 1 条 #4658 复现形状)→ 红。fixture 有效性用一个本地复刻的旧匹配器大声断言:data/Index:type 的叶名今天仍被 protocol 11 的 flow.node.type 命中,api/HttpFindQueryParams:distinct 仍被 data.query.distinct(另一个 def 的键)命中 —— 哪天不再撞车,这两个 fixture 就不再模拟缺陷,测试当场说出来。
  2. 登记一个 key 只登记那一个 key:同叶名的邻居照样红。
  3. 两个都按确切名登记 → 检查 (b) 静默(唯一还欠的是重新生成基线)。
  4. 登记项指向一个仍然 live 的 key → (b2) 红。
  5. 登记项指向一个本次构建已不产出的 key → 绿(老化稳态)。
 Test Files  1 passed (1)
      Tests  36 passed (36)     ← 31 个既有(#5807/#5851 的沙盒用例)全绿 + 5 个新增

全包:

 Test Files  322 passed (322)
      Tests  8254 passed (8254)
$ pnpm --filter @objectstack/spec typecheck
tsc --noEmit ✓
check:test-typecheck: OK
$ pnpm --filter @objectstack/spec check:generated
✓ All 10 generated artifacts are up to date.

check:dual-source-exports / check:exported-any / check:nul-bytes 亦绿。

反向验证 —— 方向先定,再跑

把检查 (b) 还原成叶名匹配、并摘掉 (b2),重跑这 5 个用例。预判 3 红 2 绿,实测一致:

 Tests  3 failed | 2 passed | 31 skipped (36)
  • 红的是钉住本次改动的三条:用例 1(旧匹配器静默,2 key(s) were tombstoned… 从未出现)、用例 2、用例 4(旧代码根本没有 (b2),整轮 exit 0)。
  • 绿的两条是构造使然,不是漏网:用例 3 是绿路径断言 —— 旧匹配器同样让这两个 key 通过(正是靠那次撞车),所以两侧都绿;用例 5 断言的是一种容忍,旧代码没有 (b2) 可以触发,自然也绿。绿路径与容忍断言在还原实验里不可能变红,这里如实记下,不硬凑成「全红」。

范围外发现


Generated by Claude Code

claude added 2 commits August 6, 2026 11:13
`build-schemas.ts` 检查 (b)(`check:authorable-surface`)此前判定「这次退休
已登记」的方式是:取 key 的叶名,和全部 major 的所有 conversion / migration
`surface` 子句做 `endsWith('.' + name)`,完全不看 key 属于哪个 def。任何无关
登记只要 surface 以同名叶子结尾,就替这个墓碑背书。#4658 实测:
`automation/Event:type` 零 conversion 静默通过,命中的是 protocol 11 的
`flow.node.type`;#5509 之后 `.description` 也进了这个免检名单。

- 新增导出 `RETIRED_KEYS_BY_MAJOR`(`src/migrations/registry.ts` 末尾追加,
  未改动该文件任何既有行),值是确切的 `${defKey}:${name}`。
- 检查 (b) 改为对该表精确集合判定;失败信息直接打印要粘贴的那一行和 major。
- 新增检查 (b2):登记了一个仍然 live 的 key 直接失败;登记了一个本次构建已不
  再产出的 key 不是错误(墓碑老化后的预期稳态)。
- conversion 的 `surface` 散文一字未动;检查 (c) 的叶名匹配保留,原因与后续
  处置记在 #5898。
- 退休 playbook(`.claude/skills/spec-property-retirement/SKILL.md`)同步:
  原先那条「surface 必须以裸 key 结尾」正是本单的缺陷,已改写。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01559M8FVm6W6vDLABL3jvdW
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 11:21am

Request Review

@github-actions github-actions Bot added the size/l label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

110 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 6, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review August 6, 2026 11:36
@baozhoutao
baozhoutao added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 7adc841 Aug 6, 2026
25 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-4659-retired-keys-registry branch August 6, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

build-schemas.ts 检查 (b) 用叶名匹配 conversion surface —— 无关簇的 .type 就能让一个 tombstone 冒充「已登记迁移」

2 participants