Skip to content

Commit 2598216

Browse files
os-zhuangclaude
andauthored
fix(spec): 检查 (c) 的墓碑老化时钟按确切 key 起算,不再用叶名匹配 (#5898) (#6256)
检查 (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。 Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5 Co-authored-by: Claude <noreply@anthropic.com>
1 parent de6b7f1 commit 2598216

5 files changed

Lines changed: 451 additions & 96 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
fix(spec): 墓碑老化时钟改按**确切 key** 起算 —— 无关簇的登记不再替一次退休提前放行 (#5898)
6+
7+
`scripts/build-schemas.ts` 的检查 (c)(#4650)保证一件事:删掉一条
8+
`authorable-surface/` 基线行必须自带证明 —— 那个文件正是检查 (a)/(b) 赖以判定的
9+
证据,行没了证据也就没了。它承认的三种证明里,第一种是「墓碑已老化」:base 里这条
10+
`[RETIRED]`,且它的退休登记比当前 major 至少早 `TOMBSTONE_AGE_MAJORS`(= 2)。
11+
12+
#4659 把检查 (b) 收口到了 `RETIRED_KEYS_BY_MAJOR` 的确切 key,检查 (c) 的同一套
13+
**叶名匹配**原封不动地留了下来:拿 key 的叶名去和**全部 major** 的所有 conversion /
14+
migration `surface` 子句做 `endsWith('.' + prop)`,再取 `Math.min`。两个后果都朝
15+
「放行」的方向 —— 一条无关登记就能让一个从没被登记过的墓碑通过「有没有登记」这一关,
16+
`Math.min` 保证时钟一律从**最早**的那次巧合起算。
17+
18+
实测:当前 97 条历史墓碑里有 **2** 条今天就可删,而且**两条都是误判**,机制还不一样:
19+
20+
- `data/Index:type` 被 protocol 11 的 `flow.node.type` 定了年份 ——
21+
`flow-node-http-callout-rename` 里一个 flow 节点的 `type`,和索引类型毫无关系。
22+
它自己那条诚实的登记 `object.indexes[].type` 是 major 17,时钟被提前了六个 major。
23+
- `api/RestApiConfig:requireAuth` 被 major 12 的 `api.requireAuth` 定了年份 ——
24+
那是 `rest-requireauth-default-flip`,一次**安全默认值翻转**,该 step 自己写着
25+
「No metadata shape changed」。它真正的退休是 protocol 17 的 conversion
26+
`stack.api.requireAuth`(#3963)。同一个 surface,不同**种类**的变更,早了五个 major。
27+
28+
现在检查 (c) 读的是检查 (b) 那张表,按确切 `` `${defKey}:${name}` `` 判定,
29+
`build-schemas.ts` 里再没有任何叶名匹配。
30+
31+
**历史墓碑不回填,并且因此不可删。** 两条可机械推导的来源都无法诚实定年:叶名匹配
32+
正是 #4659 拿掉的那种推断(上面两条误判即为实证);而
33+
`authorable-surface.json` 的 git 历史始于 `17.0.0-rc.0`,把 97 条全部定在 major 17
34+
—— 那是基线文件的**出生日期**,不是考据。所以这些行保持未登记,检查 (c) 对它们
35+
fail-closed:没有条目就无法证明年龄,基线行不许删。可删数从 **2 → 0**(按当前
36+
`authorable-surface/` 的 100 条墓碑口径同样是 2 → 0)。
37+
38+
要删其中某一行,是一次有意的、可复核的动作:确定该 key 真正的退休 major,把确切 key
39+
写进 `RETIRED_KEYS_BY_MAJOR`,由检查 (b2) 复核该条目仍指向一个本次构建确实
40+
tombstone 的 key。⚠ 定不出年份的行不要写 —— 写进这张表的估算,对之后每一道门禁都
41+
读作事实。
42+
43+
导出值本身没有变化;`RETIRED_KEYS_BY_MAJOR` 的文档注释更新为它现在同时被检查 (b)/(b2)/(c)
44+
读取,以及历史墓碑的 fail-closed 口径。

.claude/skills/spec-property-retirement/SKILL.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,14 +202,24 @@ follows. Write both.
202202
ignored), so `dashboard.aria` registered `ui/FormView:aria` and protocol
203203
11's `flow.node.type` registered any `.type` at all (#4658). The guarantee
204204
had lapsed for every common leaf.
205+
*The entry also starts the aging clock (#5898):* gate (c) reads the same
206+
table to decide when this tombstone's `authorable-surface/` line may be
207+
deleted (~two majors), so the major you write it under is the release the
208+
clock counts from. Retirements older than the table are **undeclared and
209+
therefore not deletable** — nothing could date them honestly (leaf matching
210+
dated `data/Index:type` from an unrelated `flow.node.type` at major 11, and
211+
the baseline file's own git history starts at 17.0.0-rc.0). To delete one of
212+
those lines, establish its true major, add the entry, and say so in the PR.
213+
⚠ Never add a row you cannot date: an estimate here reads as a fact to
214+
every later gate.
205215
- [ ] **`surface` stays prose — it is no longer matched.** Write it the way an
206216
author writes metadata (`flow.nodes[].outputSchema`), which is what the
207217
upgrade guide prints. Multi-key conversions still join clauses with exactly
208218
`' / '` (house style since the tool sweep). Nothing downstream parses it
209-
for attribution any more — that job moved to the entry above. ⚠ One
210-
consumer still does read the clauses by leaf: gate (c)'s *aged-out
211-
tombstone* proof, which adjudicates retirements older than
212-
`RETIRED_KEYS_BY_MAJOR` and could not be moved with it (#5898).
219+
for attribution any more — that job moved to the entry above. Since #5898
220+
that is true of **every** consumer: gate (c)'s *aged-out tombstone* proof
221+
was the last leaf matcher and now reads the same exact-key table, so no
222+
rule anywhere parses a `surface` for attribution.
213223
- [ ] **`retiredFromLoadPath: true`** — for a retirement, always. Two distinct
214224
justifications, and they are not interchangeable: for a *rename* it means
215225
"no alias window, deliberately" (the tombstone owns the refusal; the entry

0 commit comments

Comments
 (0)