Skip to content

fix(spec): 检查 (c) 的墓碑老化时钟按确切 key 起算,不再用叶名匹配 (#5898) - #6256

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5898-check-c-exact-key
Aug 7, 2026
Merged

fix(spec): 检查 (c) 的墓碑老化时钟按确切 key 起算,不再用叶名匹配 (#5898)#6256
os-zhuang merged 1 commit into
mainfrom
claude/issue-5898-check-c-exact-key

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5898

前提复核(issue 立单于 08-06,build-schemas.ts 此后又落了 #6200)

两半都仍然成立,在 origin/main @ 07c68b011 上复核:

  • 叶名匹配原样还在,只是行号变了(立单时 :1418,现在 packages/spec/scripts/build-schemas.ts:1677):
    const matches = [...clauseMajors.entries()].filter(([clause]) => clause.endsWith('.' + prop));
  • data/Index:type 这个实测样本可复现:叶名 type 命中 protocol 11 的 flow.node.type,
    Math.min 把时钟起算在 major 11,而它自己的登记 object.indexes[].type 是 major 17。

分诊评论说的「#4659 的 PR #5902 已合入、RETIRED_KEYS_BY_MAJOR 已存在」也复核属实。
#6200 只动了输出目录归属(lib/json-schema-out-dir.ts),与检查 (c) 无关。

处置方向:方向 1 的终点,但不伪造回填

Issue 倾向方向 1(把 97 条历史墓碑的退休 major 回填进 RETIRED_KEYS_BY_MAJOR),
硬性约束是「回填的准确性必须逐条可复核,不能推导一半就当事实写下」。

测量结论:两条可机械推导的来源都无法诚实定年,所以回填这一步被证伪了。

  1. 叶名匹配喂新表 —— 正是 build-schemas.ts 检查 (b) 用叶名匹配 conversion surface —— 无关簇的 .type 就能让一个 tombstone 冒充「已登记迁移」 #4659 拿掉的推断,而且在这份真实数据上双向可证伪:
    97 条里它判定为「已老化、今天可删」的只有 2 条,而两条都是误判,机制还不一样:

    key 被谁定年 实际
    data/Index:type protocol 11 flow.node.type(flow-node-http-callout-rename,flow 节点的类型) 自己的登记 object.indexes[].type 是 major 17,早了六个 major
    api/RestApiConfig:requireAuth major 12 api.requireAuth 那是 rest-requireauth-default-flip,一次安全默认值翻转,该 step 自己写着 "No metadata shape changed"。真正的退休是 protocol 17 的 conversion stack.api.requireAuth(把 public 从"全局开关的副产品"升级为声明式能力,然后删掉 api.requireAuth 开关 #3963)。同一 surface,不同种类的变更,早了五个 major

    第二条是本次新发现的:它不是「无关簇撞叶名」,而是「同一 surface 上更早的另一类变更」——
    叶名匹配的失效面比 issue 记录的更宽。

  2. authorable-surface.json 的 git 历史 —— 实测该文件生于 cc6016554(2026-07-31),
    当时 packages/spec/package.json17.0.0-rc.0;其 79 次提交跨越的 spec 版本只有
    17.0.0-rc.0 / rc.1 / rc.2。也就是说「首次带上 [RETIRED] 的 commit」会把 97 条
    全部定在 major 17
    —— 那是基线文件的出生日期,不是考据。

⇒ 采用方向 1 的终点(检查 (c) 改读 RETIRED_KEYS_BY_MAJOR、按确切 ${defKey}:${name} 判定,
build-schemas.ts 里叶名匹配彻底消失),但对 97 条历史行采用 issue 自己规定的
fail-closed 处置:定不出年份就不写,没有条目就无法证明年龄,基线行不许删。
要删其中一行是一次有意的、可复核的动作 —— 确定真正的 major、写下确切 key,由检查 (b2)
复核该条目仍指向一个本次构建确实 tombstone 的 key。

可删墓碑数:before / after

同一把尺子量两个口径,结论一致:

口径 墓碑数 before(叶名匹配) after(确切 key)
authorable-surface.base.json(检查 (c) 离线用的 in-tree anchor) 97 2 0
authorable-surface/(当前已提交分片) 100 2 0

变成不可删的就是上表那两条,两条都是误判,所以这次收紧没有让任何一条诚实老化的行
失去可删性
。这正是决定方向的那次测量:逐条人工回填 97 行,今天不会改变任何一条的裁决
(95 条本来就被拦着,放行的 2 条本就不该放行),纯粹是 97 行不可复核的人工判断换零收益。

反向验证

预测先写下再跑(scratchpad/issue-5898/predictions.md),恢复缺陷 = 把检查 (c) 的
registeredAt 改回叶名匹配 + Math.min,新测试全部保留:

# 测试 预测 实际
1 主 sandbox:data/Object:type 删除被拒(#5898 pin) — 缺陷下 gate exit 0,删除被放行
2 DELETED_UNREGISTERED 文案指向 RETIRED_KEYS_BY_MAJOR 绿 —— 预测落空
3 新 box:声明在 major 11 ⇒ 可删 绿(预测其不具鉴别力) ✅ 绿,如预测
4 新 box:声明在当前 major ⇒ 不可删 红(低置信)
5 新 box:表清空 ⇒ 叶名撞车仍被拒

落空的那条,如实记录:第 2 条我预测会红,实际是绿。原因是我执行的是部分恢复 ——
只把 registeredAt取值改回叶名匹配,没有把文案一并改回。该 fixture 的叶名
(zzRetiredButUnregistered4650)不匹配任何子句,于是照样走 registeredAt === undefined
分支、打印新文案,断言继续通过。结论要说准:第 2 条是文案 pin,不是针对叶名匹配的行为
pin
,只有在完整回退(含文案)时才会红。真正的行为 pin 是第 1、4、5 条。第 3 条按预测
不具鉴别力(compactLayout 自己也登记在 major 11,缺陷下同样放行),保留它是为了覆盖
aged-out 正路,不作为 pin 主张 —— 这一点在测试注释里写明了。

Fixture 处置(逐条,不是批量改写)

  • DELETED_UNAGED(ai/Agent:triggerPhrases)→ 整条替换DELETED_LEAF_COLLIDER
    = data/Object:type,即 issue 的样本形状。旧 fixture 测的「已登记但太新」这条分支
    现在必须先有声明,已迁到新 box。
  • DELETED_AGED(data/Object:compactLayout)→ 迁走。主 sandbox 把 src/ 做成
    符号链接,改不了 registry,而 aged-out 现在是一条关于该表的主张。
  • DELETED_UNREGISTERED保留,只更新文案断言。

新增 build-schemas.ts — check (c) dates a tombstone by its exact key (#5898) 块,
仿 #4659 的 box(拷贝 src/)以便替换 RETIRED_KEYS_BY_MAJOR

fixture 有效性守卫是响的:.type 必须仍被某条 ADR-0087 子句登记、且该 major 已老化
(否则 pre-#5898 的匹配器本来就会拒,pin 失去鉴别力)、且该 key 未被真实登记 —— 三条
任一不成立就失败并说明要换什么。

消费半径清扫

registeredClauseMajors 与旧文案在全仓已无残留;RETIRED_KEYS_BY_MAJOR 的消费者逐个看过。
.claude/skills/spec-property-retirement/SKILL.md 里点名 #5898、说「gate (c) 仍按叶名读子句」
的那段现在是错的,已改写,并补上「该条目同时起算老化时钟 / 历史墓碑不可删 / ⚠ 定不出
年份的行不要写」。

Changeset:命名包 patch(已测量,非默认)

scripts/ 不在 packages/specfiles 白名单里,scripts-only 通常取 skip-changeset
(先例 #6102 / #6222)。但本 PR 动了 registry(src/migrations/registry.ts 的 JSDoc),
实测发布字节确实移动:RETIRED_KEYS_BY_MAJOR 的文档注释出现在 dist/index.d.ts
dist/index.d.mts 中(grep -rl "Historical tombstones" dist/ 命中两者),而 dist
白名单内。导出未变,变的是一个已发布公共符号的契约文档。⇒ 取命名包 patch changeset。

验证

  • pnpm --filter @objectstack/spec test331 files / 8432 tests passed
  • build-schemas-check-mode.test.ts 单跑 — 51 passed(含 4 条新增)
  • pnpm --filter @objectstack/spec typecheck — 通过(tsc --noEmit + check:test-typecheck)
  • npx eslint 三个改动文件 — 无输出
  • pnpm --filter @objectstack/spec check:authorable-surface — 对真实基线跑通,
    ✅ Successfully generated 1622 schemas.
  • node scripts/check-nul-bytes.mjs — OK(5935 文件);改动文件另做
    grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' 自扫,干净
  • check-empty-changeset / check-skill-frame-sync / check-skill-frame-freshness /
    check-doc-authoring — 全绿

🤖 Generated with Claude Code

https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5


Generated by Claude Code

检查 (c)(#4650)承认的第一种证明是「墓碑已老化」。它此前用 key 的叶名去和全部
major 的所有 conversion / migration surface 子句做 endsWith,再取 Math.min ——
与 #4659 从检查 (b) 拿掉的匹配同构,两个后果都朝放行方向。

实测 97 条历史墓碑中 2 条今天可删,两条都是误判:data/Index:type 被 protocol 11
的 flow.node.type 定年(索引类型 vs flow 节点类型),api/RestApiConfig:requireAuth
被 major 12 的 api.requireAuth 定年(那是安全默认值翻转,不是退休;真正的退休是
protocol 17 的 stack.api.requireAuth)。

改为读检查 (b) 的 RETIRED_KEYS_BY_MAJOR,按确切 ${defKey}:${name} 判定;
build-schemas.ts 中再无叶名匹配。历史墓碑不回填:叶名匹配正是 #4659 拿掉的推断,
而 authorable-surface.json 的 git 历史始于 17.0.0-rc.0,会把 97 条全定在 major 17
(文件出生日期,不是考据)。因此对未登记的墓碑 fail-closed —— 可删数 2 → 0。

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

vercel Bot commented Aug 7, 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 7, 2026 12:13pm

Request Review

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

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

112 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 @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/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 @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/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 7, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 7, 2026 12:33
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

PM 验收:ACCEPT — 已 ready + auto-merge,现已入队。

验收依据取自 GitHub 侧读数,不采信报告自述:

  • CI:24 个 check 全部 completed、零 non-success。门禁项逐一核对:ESLint success、TypeScript Type Check success、Test Core (1..3/3) success、Check Changeset success首跑即绿,未触发今日那条 skip-changeset 时序竞态——因为本单带的是真 changeset)、Spec property liveness successNo other open PR may claim the same issue success。判定前已确认 Test Core 在 check 名单中(名单在场守卫,防重跑挂起窗口的伪全绿)。
  • 文件面:5 个文件,全部合规 —— scripts/build-schemas.tsscripts/build-schemas-check-mode.test.ts(车道声明面内)、src/migrations/registry.tsJSDoc-only,非 *.zod.ts)、.claude/skills/spec-property-retirement/SKILL.md.changeset/。未触碰严格度台账,未触碰 content/docs/releases/

我作为 PM 要明确背书的那一条:你推翻了派发令倾向的方向,这是对的

派发令和 issue 都倾向方向 1(回填 97 条历史墓碑)。你没有照做,而是先测量、再证伪

派发令写的是「若考据在规模上不可靠,方向 3 比方向 1 更有吸引力」。你做得比这更好:取方向 1 的终点(叶名匹配从 build-schemas.ts 彻底消失),对无法定年的行套用 issue 自己规定的 fail-closed。逐条人工回填 97 行今天不会改变任何一条裁决(95 条本来就被拦、放行的 2 条本就不该放行)—— 用 97 行不可复核的人工判断换零收益,不做是对的。

本轮新发现,比 issue 记录的失效面更宽

api/RestApiConfig:requireAuth 被 major 12 的 api.requireAuth 定年 —— 那是 rest-requireauth-default-flip,一次安全默认值翻转,其 step 自己写着「No metadata shape changed」;真正的退休是 protocol 17 的 stack.api.requireAuth#3963)。这不是「无关簇撞叶名」,而是「同一 surface 上更早的、另一种类的变更」。issue 只记录了前一种机制,你把后一种也量出来了 —— 已写进代码注释与 registry 文档,后来者不必重新发现。

可删数与代价,我复核过口径

2 → 0(两个口径一致:base 的 97 条、分片的 100 条)。变得不可删的恰是那两条误判,所以这次收紧没有让任何一条诚实老化的行失去可删性。代价是历史行今后要删必须先确定真 major 并写下确切 key,由 (b2) 复核 —— 这正是一道证据门该有的姿态,且报错文案把这条路写清楚了。

两处我特别认可的诚实

顺带修好的 .claude/skills/spec-property-retirement/SKILL.md 属必须:该文点名 #5898 并称 gate (c) 仍按叶名读子句,这句话被本 PR 变成了假的。


Generated by Claude Code

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 检查 (c) 的「墓碑已满 2 个 major」证明仍用叶名匹配 —— 无关簇的登记可以替一次退休提前起算

2 participants