Skip to content

Commit daedf1f

Browse files
docs(adr-0087): D3 carries one semantic entry per retirement family, even when D2 is lossless (#20254)
Fixes #20188 Clause-②: no ## What Four governed-text sites still taught the D3 rule from before ruling B. This PR brings all four in line with it. Ruling B says every retirement family gets exactly one ADR-0087 D3 `semantic` entry, even when a lossless D2 conversion also exists, and D2 carries the mechanical data repair only. 1. **`docs/adr/0087-metadata-protocol-upgrade-contract.md`**: two parentheses change, nothing else. - D3 paragraph (`:176` on the base): 「**semantic changes** authored for that major (the residue D2 cannot express losslessly)」 becomes 「**semantic changes** authored for that major (one entry per retirement family, even when a lossless D2 conversion also exists for it; D2 carries the mechanical data repair only)」. - Ratified pre-launch exemption (`:383–:384` on the base): 「every such break must land as a chain step (a `retiredFromLoadPath` conversion when lossless, a semantic TODO when not) in the same release」 becomes 「every such break must land as a chain step (a `retiredFromLoadPath` conversion when lossless, and one D3 semantic entry per retirement family in every case; D2 carries the mechanical data repair only) in the same release」. - Each edited line is split in two, so the file grows two lines. No other line of either paragraph is reflowed. The status line is unchanged, and the D2-scope sentence at `:152` (「Semantic changes are excluded. …」) is untouched because it states D2's scope and does not contradict the ruling. - The wording copies the two code-side sites already on `main`: the `packages/spec/src/migrations/registry.ts` header (`:9`) and the `packages/spec/src/migrations/types.ts` docblock. 2. **`.claude/skills/spec-property-retirement/SKILL.md` §3** (the registration checklist): - The ⚠ preamble now counts **three** independent obligations: the registry entry (read by the gate), the conversion (followed by the consumer) and the D3 entry (read by the upgrading agent). - The response-surface item (`:234` on the base) is rewritten as a checklist item that every retirement owes: 「每个退役家族一条 D3 `semantic` 条目(`SemanticMigration`;按家族,不按键),即使 D2 conversion 无损也欠 —— D2 只承载机械数据修复」. The item keeps the non-empty `reason` / `acceptanceCriteria` requirement and points to the one-file-per-entry layout in `packages/spec/src/migrations/entries/`. - Response-surface keys with no rewritable source are still covered. They are now the case where D3 is the **only** route, no longer the only case that takes a D3 entry. The `EnhancedApiError.fieldErrors` example stays. 3. **`.claude/skills/spec-property-retirement/SKILL.md` §4** (the surface list, `:251`): 「Conversion + 链步 + 精确键 `RETIRED_KEYS_BY_MAJOR` 条目(§3)」 becomes 「Conversion + 链步 + 精确键 `RETIRED_KEYS_BY_MAJOR` 条目 + 每家族一条 D3 `semantic` 条目(§3)」. - It is a same-line edit, so a dev who works from the §4 list alone no longer skips the D3 entry. - The line is not a table row, so the ratchet's table-row width pin (326 bytes) does not apply. No line was added. ## Why - The ruling was recorded on the retirement-family card as a class-one self-adjudication (comment `5615360777`, restated at `5634031140`). - Its authority is the maintainer's ruling on #15954, comment `5559778263`, verbatim 「one ADR-0087 D3 semantic entry **per family** … its D3 entry says so」. - The code-side half already landed in PR #20181. Before this PR, the ADR and the retirement playbook were the last two places that taught the old rule. A retirement author who followed either one shipped no D3 entry for a family whose D2 conversion was lossless. The spec half of that cleanup is tracked separately: #20201 remains open. - The skills seat judged that the playbook **should** state the rule. It is the one checklist a retirement dev works from, so leaving the rule out would let the next retirement miss it again. ## Line budget (the skill ratchet has headroom 0 on this file) | file | before | after | ceiling | |---|---|---|---| | `docs/adr/0087-metadata-protocol-upgrade-contract.md` | 960 | 962 | none (not in the ratchet map) | | `.claude/skills/spec-property-retirement/SKILL.md` | 337 | 337 | 337 (`check:pm-skill-ratchet`) | - The playbook's two new lines are paid for by **deleting content** in the same section, not by re-wrapping. - The deleted text is the surface-prose item's closing restatement: 「#5898 起这对每个消费者都成立:门 (c) 的 aged-out tombstone 证明曾是最后一个叶匹配者,现在也读同一张精确键表,再没有任何规则从 `surface` 解析归属」. - Nothing operative is lost. The item already says 「下游不再有任何东西从它解析归属」 one clause earlier, and the retired-keys item in the same section still says that gate (c) reads the exact-key table. - The ceiling is not raised. ## Verification (all at head `75eeabfc`, the final commit) - The gate list comes from `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands`. No paths were passed: the change set was derived from merge base `e4621867` and gave 2 paths, +12 / −10. - Result: **28 commands, every one exit 0.** - Reconciliation: `dispatch-gates --ran` prints `28 derived famil(ies) accounted for — 28 run, 0 NOT-MEASURED (a DERIVED zero — all 28 recorded an exit code and none of them is 3)`. - `origin/main` was merged into the branch at `75eeabfc`, because the derivation at `65eae198` flagged a STALE TREE: two inputs it reads had changed on `main`. The PR's net diff against `main` is still exactly the two files. - The merge changed `packages/lint` sources, so `@objectstack/formula` and `@objectstack/lint` were rebuilt under the verify lock (`VERDICT command-exit 0`). `check:doc-formula-expressions` was then rerun and exited 0. - Verdict lines for the gates this change touches: - `pnpm check:pm-skill-ratchet`, exit 0: `.claude/skills/spec-property-retirement/SKILL.md is 337 lines (ceiling 337; headroom 0)` and `widest table row is 326 bytes (pin 326; headroom 0)` - `node scripts/check-adr-links.mjs`, exit 0: `690 relative link destination(s) under docs/adr/ resolve` - `node scripts/check-adr-symbol-anchors.mjs`, exit 0: `2123 anchors across 140 records resolve` - `pnpm check:adr-anchors`, exit 0: `OK (54 anchored file(s), every governing ADR still referenced …)` - `pnpm check:doc-authoring`, exit 0: `16350 customer-facing string(s) across 1093 spec sources clean` - `pnpm check:nul-bytes`, exit 0: `OK (scanned 9715 text file(s) … no raw ASCII control bytes)` - Not in the derived total, and so not measured locally: the 11 wide-population families, the 3 families whose argv needs workflow values, and the 1 CI job scheduled by these paths. CI runs them. ## Changeset - None, and `skip-changeset` applies. Both paths are in the fast lane (`docs/adr/**`, `.claude/**`). - Measured: the root `package.json` is `private: true` with no `files`, and no workspace package's `files` reaches either path. Nothing here is published. ## Acceptance notes - **Playbook §3 retired-keys item** (`:186–:188`) still says to add the `RETIRED_KEYS_BY_MAJOR` entry 「在 `packages/spec/src/migrations/registry.ts`」. - That table has since become a generated region. Entries are now one file each under `packages/spec/src/migrations/entries/retired-keys/`, and the entries README says to touch no other file. - `check:migration-registry` catches a hand edit loudly, so this is drift in the pointer, not a silent trap. - Not a defect class that gets a card; noted here. Carrier: none. ## 维护者速读(草稿) **改了什么**:两个受管文件,只改跟这条规则有关的几句。ADR-0087 里有两处还写着旧规则:D3 段说 semantic 条目「只收 D2 无法无损表达的残余」,上线前豁免段说「无损时写 conversion,否则写 semantic TODO」。两处都改成同一条规则:每个退役家族一条 D3 条目,即使 D2 已能无损转换;D2 只负责机械的数据修复。属性退役手册的 §3 清单和 §4 汇总行也补上了这条义务。 **为什么改**:这条规则已经裁定(B 方案,依据是维护者在 #15954 上的原话「one ADR-0087 D3 semantic entry per family」)。代码侧的两处注释已经改完,只剩 ADR 和手册还在教旧规则。照着旧文字做退役的人,会在 D2 无损时漏掉 D3 条目,升级方的 agent 就拿不到那一条判断提示。第 18 版已经有两个键因此漏了。 **风险与代价(含回滚)**:只改文字,不动代码、不动任何 schema 或已发布的包,不需要 changeset。ADR 多出两行;手册行数仍守在上限 337 行,新增的两行靠删掉同一节里一句重复的历史说明来抵。回滚就是 revert 这个 PR。 **席位意见**: **你要做的**:审阅这两段改动,同意就在本 PR 上批准(Approve)。 --- _Generated by [Claude Code](https://claude.ai/code/session_01MjvgiFAmjHqsxy1XLiVYfH)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent d498113 commit daedf1f

2 files changed

Lines changed: 12 additions & 10 deletions

File tree

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ conversion 已不再覆盖的那位。
174174
`spec-changes.json`、生成的 upgrade guide 与 `spec_changes` MCP 工具才是主通道,不
175175
注册它们就一直空着。
176176

177-
⚠ **这是两个独立义务,其中只有一个是字符串匹配。** registry 条目是门禁读的;
178-
conversion 是消费者跟的。两个都要写。
177+
⚠ **这是三个独立义务,其中只有一个是字符串匹配。** registry 条目是门禁读的;
178+
conversion 是消费者跟的;D3 条目是升级方的 agent 读的。三个都要写。
179179

180180
- [ ] **一条 `MetadataConversion`**,在 `packages/spec/src/conversions/registry.ts`:
181181
kebab-case 的 `id` 以 `-removed` 结尾、`toMajor`、每键一条
@@ -203,9 +203,7 @@ conversion 是消费者跟的。两个都要写。
203203
- [ ] **`surface` 保持散文 —— 它不再被匹配。** 按作者写元数据的方式写
204204
(`flow.nodes[].outputSchema`),那也是 upgrade guide 打印的。多键 conversion
205205
仍用恰好 `' / '` 连接子句(tool 清扫以来的 house style)。下游不再有任何东西
206-
从它解析归属 —— 那个职责移给了上面的条目。#5898 起这对**每个**消费者都成立:门
207-
(c) 的 *aged-out tombstone* 证明曾是最后一个叶匹配者,现在也读同一张精确键
208-
表,再没有任何规则从 `surface` 解析归属。
206+
从它解析归属 —— 那个职责移给了上面的条目。
209207
- [ ] **`retiredFromLoadPath: true`** —— 退役恒真,但管辖权只有 authoring 漏斗
210208
`normalizeStackInput`;三处 data-at-rest seam 以 `includeRetired: true` 故意重放退役
211209
条目,它**一处也拦不住**:`applyConversionsToStoredItem`(钉死)、automation
@@ -231,8 +229,10 @@ conversion 是消费者跟的。两个都要写。
231229
`retiredFromLoadPath`。你的 transform 不属于这些形状,就自己证明幂等 —— CLI
232230
e2e(`packages/cli/test/migrate-meta.e2e.test.ts`)会 replay 迁移后的快照并断
233231
言 `applied` 为空。
234-
- [ ] **没有 source 可重写的响应面键**,改注册成 `SemanticMigration`(D3
235-
`semantic[]`),`reason` 与 `acceptanceCriteria` 非空 ——
232+
- [ ] **每个退役家族一条 D3 `semantic` 条目**(`SemanticMigration`;按家族,不按键),
233+
即使 D2 conversion 无损也欠 —— D2 只承载机械数据修复。`reason`(升级方仍欠的判断)
234+
与 `acceptanceCriteria` 非空;一条一个文件,见 `packages/spec/src/migrations/entries/`
235+
的 README。**没有 source 可重写的响应面键**写不出 conversion,只走这一条 ——
236236
`EnhancedApiError.fieldErrors` 是成品示例。
237237

238238
## 4. 面清单
@@ -248,7 +248,7 @@ conversion 是消费者跟的。两个都要写。
248248
窄全盲(#3883 从 `defineAction` 的输入删了三个键,快照没动)。它的门还住在另
249249
一个 workflow(`TypeScript Type Check`,不是 `Check Generated Artifacts`),读
250250
的是构建出的 `dist/*.d.ts`。
251-
- [ ] **Conversion + 链步 + 精确键 `RETIRED_KEYS_BY_MAJOR` 条目**(§3)。
251+
- [ ] **Conversion + 链步 + 精确键 `RETIRED_KEYS_BY_MAJOR` 条目 + 每家族一条 D3 `semantic` 条目**(§3)。
252252
- [ ] **Liveness 台账** —— 按 §2 的路线表,带 `verifiedAt`。更新 README 的按类型行
253253
**连同计数**(那张表狠狠漂过一次;用 README 里的 python 片段重新生成计数,不
254254
要手改)。

‎docs/adr/0087-metadata-protocol-upgrade-contract.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ The spec ships **declarative migration artifacts per major** — `migrations/N.j
173173
per-surface transforms with machine-readable pre/post conditions plus a prose
174174
`rationale` (the one place prose is load-bearing, and it is one field, not a
175175
document). Two sources feed each major's step: **semantic changes** authored for that
176-
major (the residue D2 cannot express losslessly), and **graduated conversions** (D2
176+
major (one entry per retirement family, even when a lossless D2 conversion also exists
177+
for it; D2 carries the mechanical data repair only), and **graduated conversions** (D2
177178
entries retired from the load path). Together the steps form a **permanent, ordered
178179
chain** — the database-migration model applied to metadata source files.
179180

@@ -381,7 +382,8 @@ does both halves:
381382
- **The policy, stated:** until GA, a metadata-facing break MAY ship one-step
382383
without a load window. The exemption covers the *window* only — never the
383384
*chain*: every such break must land as a chain step (a `retiredFromLoadPath`
384-
conversion when lossless, a semantic TODO when not) in the same release.
385+
conversion when lossless, and one D3 semantic entry per retirement family in
386+
every case; D2 carries the mechanical data repair only) in the same release.
385387
After GA the full D2 ladder applies: lossless breaks ship a live conversion
386388
entry or they do not ship.
387389

0 commit comments

Comments
 (0)