docs(governed): the retirement route is retiredKey() on any shape, with the retiredAfter stamp and the step-18 registry shape - #20797
Conversation
…dAfter stamp; ADR-0087 window per entry The retirement kit's route table and AGENTS.md Post-Task Checklist step 3 now prescribe a retiredKey() tombstone whether or not the schema is strict: on a closed shape a bare deletion is loud but reports only an unrecognized key, losing both the prescription and the tsc channel (retired-key.ts header). The strictObject guidance map is named only for a spelling the shape never declared, such as a retired key's old alias, where a tombstone has no property to replace (data/mapping.zod.ts). The retiredFromLoadPath checklist item gains the required retiredAfter stamp: tsc refuses its absence, a new retirement carries the current packages/spec label, and retired-after.census.test.ts pins each value. ADR-0087's artifact-window bullet said a floor at or above the runtime replays nothing; since e956924 the door replays every retired entry whose retiredAfter the floor does not exceed. Amended in the dated style. Lines paid by deleting narrative with another home: the orphan-leg history (orphans.mts header) and the withdrawn-example sentence. Claude-Session: https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg Co-authored-by: Claude <noreply@anthropic.com>
…ape on main For protocol 18 the retirement kit no longer tells an author to append to MIGRATIONS_BY_MAJOR[18].conversionIds and extend a rationale string. On main the conversion goes only into MAJOR_18_CONVERSIONS in conversions/registry.ts, inserted at its identifier's sorted position per that list's header (CONVERSIONS_BY_MAJOR[18] and the step's conversionIds both derive from it), and the rationale gains one STEP18_RATIONALE fragment at its D3 semantic id's sorted position. The merge pins each header names refuse an append at the tail. Earlier steps keep the old wording, and the misspelled-id warning is scoped to them: it cannot happen on step 18, whose ids are derived. Claude-Session: https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Reviewed for PR #20797 (family PR: #20465 chain head, #20575 member), two commits on base ① Derived judgments1. The route — kit §2 row 1 「任何 shape」(strict 与否) and AGENTS.md step 3's first clause: right. 2. Row 2 「从未声明的拼写」→ guidance map, and the departure from the triage's example: right, and the measurement holds. 3. The term rename 「strict 删除」→「无墓碑删键」 (§2 ledger table 4. Every deleted sentence has a home — no lost rule.
5. The 6. The step-18 D3-chain item (kit 7. AGENTS.md step 3 against AGENTS.md's own header (lines 11-19): right. The rewritten clause is rule text only: one executable rule per sentence, no incident narrative, no ruling date or quotation, no issue-number citation, no model name; its provenance is the code it names ( 8. The ADR-0087 amendment against the ruling's words: right.
Check-run state read on
② Semver levelThe diff is three files — ③ Boundary flagsDev report (
Reviewer's own findings, none a FAIL:
Implemented-by: VERDICT: PASS |
维护者速读(终稿)— PR #20797 · 退役路线统一为
|
Fixes #20465
Fixes #20575
Clause-②: no
Family PR, one commit per card. Tier H as a whole (
AGENTS.md,docs/adr/**), so it stays a draft for the maintainer's review. Nothing here publishes:skip-changeset.What changed
#20465, commit
249b7836(the route, theretiredAfterstamp, and the ADR-0087 window sentence from the spec seat 4 fold5873572274):.claude/skills/spec-property-retirement/SKILL.md§2 route table. Row 1 is now "any shape, strict or not" →retiredKey()tombstone. On a strict shape a bare deletion is loud, but it reports only an unrecognized key and loses both the prescription and thetscchannel. Row 2 keeps the guidance map only for a spelling the shape never declared (a retired key's old alias, a wrong-layer pointer). Such a spelling has no property for a tombstone to replace. The example is nowdata/mapping.zod.ts. The same section's ledger table, and two lines in §4, rename "strict 删除" to "无墓碑删键", because the route table no longer names a strict deletion.retiredFromLoadPathchecklist item now names the requiredretiredAfter: 'x.y.z'.tscrefuses a retirement without it. A new retirement carries the currentpackages/spec/package.jsonlabel, andretired-after.census.test.tspins each value. The artifact door opens its window per entry from this stamp.AGENTS.mdPost-Task Checklist step 3 says the same as the kit, in one rule:retiredKey()whether or not the schema is.strict(), and a*_RETIRED_KEY_GUIDANCEentry only for a spelling the shape never declared. The closing clause now says what actually differs between the routes: a tombstone keeps its liveness-ledger row, and a key deleted without one loses it.docs/adr/0087-metadata-protocol-upgrade-contract.md, in the 2026-09-13 addendum's window bullet: the sentence "floor >= runtimereplays nothing" is struck through and amended in the dated style that ADR-0005 and ADR-0053 used on 2026-09-30. The amendment note gives the provenance (the Forward conversion never opens on unreleased main: spec still labelled 17.4.0 while main refuses 17.5.0 retirements, so artifacts built by the published 17.4.0 CLI are refused #20390 ruling A, landed ase956924e), and the status line gains an Amended entry.#20575, commit
2e3f8aa0(the step-18 D3-chain item, worded for the shape onmainafter #20535 and #20574):MAJOR_18_CONVERSIONSinconversions/registry.ts. It is inserted at its identifier's sorted position, as the list's header says.CONVERSIONS_BY_MAJOR[18]and the step'sconversionIdsboth derive from that list. Therationalegains oneSTEP18_RATIONALEfragment at the sorted position of its D3 semantic id. The merge pin that each header names refuses an append at the tail.Measured first (premise holds), at
7a09eee1retired-key.tsheader (:22–:28) gives the reason tombstones stay on closed shapes. TheacceptsNothingdoc instrict-object.tscalls a tombstone "strictly stronger than aguidanceentry". The precedents are real:action.aria(dcd3bceaa, astrictObject, whose comment ataction.zod.ts:1678says "retiredKey()rather than a bare deletion although this is astrictObject"), the list view'stabs(6e3e5462c, in the strictListViewShapeSchema), and the cube members' innername(analytics.zod.ts:243,:298).data/mapping.zod.tslists the retired keys' old alias spellings (query,onError) in its guidance map so that "an author who learned the alias should land on the prescription". Those spellings were never declared keys, so a tombstone has nothing to replace.app.zod.ts(showall,location) has the same shape.retiredAfter.conversions/types.tstypes it as REQUIRED on the retired arm of the union. The census test pins an entry that no published tarball carries retired to the package label (17.5.0on this tree).main.idsTheFloorPostdatesinartifact-forward-conversion.tsreplays a retired entry when the floor is at or below itsretiredAfter, even when the floor is at or above the runtime label (verdict'converted-retired-after'). On this tree three entries carryretiredAfter: '17.5.0', which equals the label (time-default-utc-suffix-dropped,cube-refresh-key-removed,connector-triggers-removed). An artifact whose floor is17.5.0therefore replays three entries, where the ADR said it replays none.artifact-forward-conversion.test.ts:564pins the same case one release back.step18.conversionIdsisCONVERSIONS_BY_MAJOR[18]!.map((c) => c.id), andCONVERSIONS_BY_MAJOR[18]isinApplicationOrder(MAJOR_18_CONVERSIONS).rationaleisjoinRationale(STEP18_RATIONALE). The pins arepackages/spec/scripts/conversions-major18-merge.test.tsandstep18-rationale-merge.test.ts.step17still carries a hand-keptconversionIdslist.For the reviewer: one point where the text departs from the triage wording
The #20465 triage direction says to keep the guidance map "only for the case that needs it: a shape where a tombstone cannot sit, such as a
z.preprocessstage ahead of the closed shape (retired-key.ts:177)". The route follows that direction, but the example did not hold up when measured:retired-key.ts:177describesacceptRetiredDefaultResidue. That preprocess stage strips an emitted default ahead of a closed shape that still declares the key as aretiredKey()tombstone. The tombstone sits there.tabstombstone (6e3e5462c) sits in a strict member ofViewMetadataSchema, which is itself az.preprocessahead of its union.unrecognized_keyspath of astrictObject, so a preprocess stage cannot carry one either.So both texts name the case that was measured: a spelling the shape never declared. If the maintainer means a different case, the Tier H review is the place to say so, and the kit follows.
Scope notes
:86–:87,:207and:215–:220. Consequential edits in the same file: the §2 ledger table row and the ORPHAN bullet (:102,:107), and §4:242and:257(the "strict 删除" term). The file is otherwise unchanged.aria/performance" story), whichpackages/spec/scripts/liveness/orphans.mtscarries verbatim in its header;retiredFromLoadPathitem. The rule it taught is the item's own preceding sentence and theconversions/types.tsdocblock.Line budget (
node scripts/pm/check-skill-line-ratchet.mjs, exit 0).claude/skills/spec-property-retirement/SKILL.mdlinesAGENTS.mdlinesEvery added line of the kit and of
AGENTS.mdis within 120 bytes, except table rows, which are structurally exempt. After commit 1 the kit read 335 / 337. The ratchet prints an informational hint to lower the row pin to 324; the pin lives inscripts/pm/check-skill-line-ratchet.mjs, which is outside this surface, so it is left for the owning seat.Gates, at head
2e3f8aa0These were derived by
node scripts/pm/dispatch-gates.mjs --commandsfrom this change set (31 families, the same set the dispatch named). All exited 0. Reconciled with--ran: "31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero, with every exit code recorded.check-adr-0087-registration(and its self-test),check-adr-links(and self-test; 691 links resolve),check-adr-symbol-anchors(and self-test; 2157 anchors across 140 records resolve),check-ci-filter-parity,check-closing-keyword-parity(and self-test),check-comment-mask-corpus,check-harness-current --self-test.pnpm check:adr-anchors, agent-test-spelling, changeset-gate-self-tests, cross-package-test-inputs, doc-authoring, docs-audit-scope, driver-memory-census, future-spec-major, gitlink-declared, nul-bytes, pm-governed-merges, pm-governed-prose (2 instruction surfaces name all 6 governed surfaces), pm-prior-rulings, pm-skill-id-lint (34 files clean), pm-skill-ratchet, refd-timer-probe, required-contexts, skill-frame-sync, watch-hint-literal.pnpm --filter @objectstack/lint run check:doc-formula-expressions. The first run exited 3 (PREREQUISITE NOT MET:@objectstack/formulaand@objectstack/lintwere unbuilt in the fresh worktree), which measured nothing. Afterturbo run buildfor those two packages under the verify lock, the re-run exited 0.packages/spec/src/shared/retired-key-migrate-sentence.test.tsreads this kit as part of its corpus. 1 file and 14 tests passed.Acceptance notes
RETIRED_KEYS_BY_MAJORitem still says to add the entry "inpackages/spec/src/migrations/registry.ts". Since Splitmigrations/registry.ts's two append tables into per-entry files (registry half of #6957's ruling) #7297, an entry is one file underpackages/spec/src/migrations/entries/retired-keys/followed bygen:migration-registry, and the region inregistry.tsis generated. The kit is echoing thebuild-schemas.tsgate (b) failure text, which still says to paste the key intoregistry.tsunder the current major. That gate text is the producer, and it is outside this surface. It is reported to the PM in the dev report, not filed.维护者速读(草稿)
改了什么
retiredKey()墓碑。guidance map 只留给 shape 从未声明过的拼写(如退役键的旧别名),那里没有属性可供墓碑替换。retiredFromLoadPath条目补上必填的retiredAfter版本戳。MAJOR_18_CONVERSIONS,rationale 只加一个排序片段。为什么改
retiredAfter等于当前版本标签的条目,在 floor 等于 runtime 时仍会被重放。风险与代价(含回滚)
席位意见
你要做的
Generated by Claude Code