@@ -171,10 +171,15 @@ jumping several majors at once, whom the load-path conversion no longer covers.
171171
172172## 3. Register the surface (ADR-0087 D2/D3) — or the gate stops you
173173
174- ` scripts/build-schemas.ts ` gate (b) fails any newly-tombstoned key with no
175- registered migration surface: the tombstone is audible only to whoever * hits*
176- it, while ` spec-changes.json ` , the generated upgrade guide and the
177- ` spec_changes ` MCP tool are the primary channel and would stay empty.
174+ ` scripts/build-schemas.ts ` gate (b) fails any newly-tombstoned key that is not
175+ registered by its ** exact** ` ${defKey}:${name} ` in ` RETIRED_KEYS_BY_MAJOR ` : the
176+ tombstone is audible only to whoever * hits* it, while ` spec-changes.json ` , the
177+ generated upgrade guide and the ` spec_changes ` MCP tool are the primary channel
178+ and would stay empty.
179+
180+ ⚠ ** This is two separate obligations, and only one of them is a string match.**
181+ The registry entry is what the gate reads; the conversion is what a consumer
182+ follows. Write both.
178183
179184- [ ] ** A ` MetadataConversion ` ** in ` packages/spec/src/conversions/registry.ts ` :
180185 kebab-case ` id ` ending ` -removed ` , ` toMajor ` , one
@@ -183,13 +188,27 @@ it, while `spec-changes.json`, the generated upgrade guide and the
183188 not the item count. Walkers (` mapCollection ` , ` mapFlowNodes ` , ` renameKey ` )
184189 live in ` conversions/walk.ts ` and are copy-on-write — return the input
185190 reference untouched when nothing matched.
186- - [ ] ** ` surface ` must end with the bare key.** The matcher is
187- ` surfaces.some((s) => s.endsWith('.' + key)) ` after
188- ` .flatMap((s) => s.split(' / ')) ` . Multi-key conversions join clauses with
189- exactly ` ' / ' ` (house style since the tool sweep) and ** each clause must
190- end with its own key** . Caveat: only the last dotted segment is compared,
191- so the schema name is never checked — ` dashboard.aria ` would satisfy
192- ` ui/FormView:aria ` . Don't lean on the gate for attribution.
191+ - [ ] ** A ` RETIRED_KEYS_BY_MAJOR ` entry** in
192+ ` packages/spec/src/migrations/registry.ts ` — the literal
193+ ` '<defKey>:<name>' ` as ` authorable-surface.json ` spells it, minus the
194+ ` [RETIRED] ` mark, under this major. This is the string gate (b) reads, by
195+ exact set membership; nothing is inferred and nothing radiates from a
196+ neighbouring key. The gate's failure prints the line to paste. ⚠ Do ** not**
197+ add the entry before the tombstone lands: an entry naming a key that is
198+ still live fails gate (b2) as a registration nothing consumed.
199+ * Why a second table:* until #4659 gate (b) matched the key's ** leaf** against
200+ every registered ` surface ` (` endsWith('.' + name) ` , all majors, def
201+ ignored), so ` dashboard.aria ` registered ` ui/FormView:aria ` and protocol
202+ 11's ` flow.node.type ` registered any ` .type ` at all (#4658 ). The guarantee
203+ had lapsed for every common leaf.
204+ - [ ] ** ` surface ` stays prose — it is no longer matched.** Write it the way an
205+ author writes metadata (` flow.nodes[].outputSchema ` ), which is what the
206+ upgrade guide prints. Multi-key conversions still join clauses with exactly
207+ ` ' / ' ` (house style since the tool sweep). Nothing downstream parses it
208+ for attribution any more — that job moved to the entry above. ⚠ One
209+ consumer still does read the clauses by leaf: gate (c)'s * aged-out
210+ tombstone* proof, which adjudicates retirements older than
211+ ` RETIRED_KEYS_BY_MAJOR ` and could not be moved with it (#5898 ).
193212- [ ] ** ` retiredFromLoadPath: true ` ** — for a retirement, always. Two distinct
194213 justifications, and they are not interchangeable: for a * rename* it means
195214 "no alias window, deliberately" (the tombstone owns the refusal; the entry
@@ -243,7 +262,7 @@ Work top to bottom; each line has a gate behind it.
243262 keys from ` defineAction ` 's input and the snapshot did not change). Its gate
244263 also lives in a different workflow (` TypeScript Type Check ` , not
245264 ` Check Generated Artifacts ` ) and reads the built ` dist/*.d.ts ` .
246- - [ ] ** Conversion + chain step** (§3).
265+ - [ ] ** Conversion + chain step + the exact-key ` RETIRED_KEYS_BY_MAJOR ` entry ** (§3).
247266- [ ] ** Liveness ledger** — per §2's route table, with ` verifiedAt ` . Update the
248267 README's per-type row ** and its counts** (that table has drifted badly
249268 once; regenerate the counts with the python snippet in the README rather
0 commit comments