Skip to content

Commit d3e6740

Browse files
committed
docs(adr): ADR-0145 — mirror pairs retire together; one release clock; registry cited
Contract review on PR 27251 (comment 6470170569): - D3: a registered mirror pair (or a z.input/z.infer-derived TS member) leaves together or not at all; zod-mirror-parity's MirroredUndeclaredKeys is the pin. Applying step 4 deletes member, JSDoc and twin together. - D1: "major" is the fixed group's major, lockstep with PROTOCOL_VERSION; the deletion gate reads CURRENT_MAJOR off the spec package version; a changeset's level is a separate axis. "Never outlives the kit" yields to D3's holds. - D2: one tag clock for every R (first refused/carried at a .0.0 cut counts in the major before it); a guidance's retiredIn names the first refusing release. - Registry: cite #26080 ruling A and the anchor checker's root-only resolution, and Prime Directive 13's own wording. Claude-Session: https://claude.ai/code/session_01JQwxbYznNtcYGX8apHQPtk Co-authored-by: Claude <noreply@anthropic.com>
1 parent 01db912 commit d3e6740

1 file changed

Lines changed: 29 additions & 15 deletions

File tree

‎docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md‎

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ accept is the defect it names) and Post-Task Checklist step 3 (the changeset).
1717
**Governs**: every `?: never` member on the ui TypeScript authoring face — `ui/packages/types` and any `.d.ts` another
1818
`ui/packages/*` package publishes — and the zod twin that mirrors it. ⛔ Not a live `@deprecated` member (an alias the
1919
runtime still accepts): it is not a tombstone, and its retirement is its own card's.
20-
**Registry**: the root `docs/adr/` (Prime Directive #13: one registry per repository). `ui/docs/adr/` is the imported
21-
ObjectUI sequence, kept in place, and gains no record here.
20+
**Registry**: the root `docs/adr/`. The ruling's words were "objectui `docs/adr/**`", written before the card moved into
21+
this repository. Here `ui/docs/adr/` is the imported ObjectUI sequence, files untouched and indexed under the `ui-`
22+
prefix (#26080, ruling A), and `scripts/check-adr-anchors.mjs` resolves a bare `ADR-NNNN` against the root registry
23+
only, spelling a ui record `ObjectUI ADR-NNNN`; so a new decision governing `ui/` code lands here (Prime Directive #13:
24+
an ADR lives in the repository whose code it governs).
2225

2326
---
2427

@@ -53,27 +56,34 @@ Spelled on ADR-0087's clock, with **R** the protocol major the retirement belong
5356

5457
- the tombstone is carried through major **R + 1**, and is **due** from the cut of major **R + 2** — the same cut at
5558
which the spec's kit for a key retired in R leaves (ADR-0087, 2026-10-26 addendum);
56-
- "major" is the **protocol** major (`PROTOCOL_VERSION`, the `CURRENT_MAJOR` the spec's deletion gate reads), ⛔ never
57-
the npm major: ADR-0087's pre-launch section keeps the two apart, and the `@object-ui/*` and `@objectstack/*`
58-
packages release as one fixed group, so one clock serves both faces;
59+
- "major" is the major of the one `fixed` release group: `@objectstack/*` and `@object-ui/*` release together, lockstep
60+
with `PROTOCOL_VERSION` at every cut, and the spec's deletion gate reads its `CURRENT_MAJOR` off
61+
`@objectstack/spec`'s package version — so one clock serves both faces. A changeset's *level* is a different axis
62+
(ADR-0087's 2026-09-13 amendment, `scripts/check-changeset-no-major.mjs`) and moves nothing here;
5963
- the window binds in both directions: a tombstone that names a spec retirement ⛔ never leaves while the spec still
60-
carries that key's kit (the ui author would lose the remedy the spec still prints), and ⛔ never outlives it.
64+
carries that key's kit (the ui author would lose the remedy the spec still prints), and ⛔ never outlives it — except
65+
as D3 holds it: a named refusal D3 keeps outlives the kit by design (the `agedOutSpecKeyRefusal` twins are that case).
6166

6267
### D2 — Reading R, including a retirement that predates ADR-0087
6368

6469
- **The tombstone names a spec retirement** (its JSDoc or zod guidance names the `@objectstack/spec` key or release it
6570
mirrors): R is the spec major that retired the key — the `RETIRED_KEYS_BY_MAJOR` row where one exists, otherwise the
66-
spec release clock ADR-0087's deletion gate uses (a key already `[RETIRED]` on the authorable surface at
67-
`@objectstack/spec@<M>.0.0` retired in a major below M).
71+
release-tag clock ADR-0087's deletion gate uses (a key already `[RETIRED]` on the authorable surface at
72+
`@objectstack/spec@<M>.0.0` retired in a major below M). ⚠️ A ui guidance that says a key "was retired in
73+
@objectstack/spec 17.0.0" (`agedOutSpecKeyRefusal`'s `retiredIn`) names the first release that **refused** it; by the
74+
gate's clock that key was already `[RETIRED]` at `17.0.0`, so R is 16, not 17. Reading the guidance's number as R
75+
runs a window one major longer than the spec's, which D1 forbids.
6876
- **The spec retirement predates ADR-0087**, so no conversion was ever registered for it — the pre-launch one-step
6977
breaks of majors 12–15 that ADR-0087's "Ratified: the pre-launch launch-window exemption" section records, or a
7078
retirement older than D2 itself: it is counted **as if its conversion had run the one-major window from the major it
71-
retired in**. R is the major of the first spec release that refused the key. No such tombstone is granted a fresh
79+
retired in**. R is read on the same tag clock: the major of the first spec release that refused the key, where a key
80+
first refused by a major's `.0.0` cut counts in the major before it. No such tombstone is granted a fresh
7281
window by this record: with the protocol at major 18, any whose R is 16 or below is already due, and the first
7382
application batch takes it.
7483
- **The tombstone names a ui-only retirement** (an objectui or ui card, no spec key behind it): the same window runs
7584
from the ui's own retirement. R is the major of the first `@object-ui/types` release tag (or the first release of the
76-
package that publishes the `.d.ts`) whose tree carries the tombstone.
85+
package that publishes the `.d.ts`) whose tree carries the tombstone, on the same convention: first carried by a
86+
major's `.0.0` cut counts in the major before it.
7787
- **Where R cannot be read** (no tag, no row, a history the reader cannot reach): the tombstone is not due. The batch
7888
reports it as undated, ⛔ never guesses a date — a wrong date removes a remedy early, which is option B by accident.
7989

@@ -90,8 +100,12 @@ strips unknown keys; the same test applies on both ui faces, judged per key:
90100
from it is **kept in silence** by the tolerant face (the case `agedOutSpecKeyRefusal` in `zod/tombstone.zod.ts`
91101
exists for: the ui keeps its own named refusal once the spec's tombstone ages out). Such a twin stays, as the ui's
92102
refusal, until its container refuses unknown keys — the posture the spec's strip-container tombstones keep.
93-
- **Derived faces move together.** Where the TS member is derived from the zod twin (`z.input` / `z.infer`), the two
94-
leave together or neither does.
103+
- **A mirror pair moves together — the key is held, not one face.** Where the TS type and its zod schema are a
104+
registered mirror pair (`ui/packages/types/src/__tests__/zod-mirror-parity.test.ts`), or the TS member is derived from
105+
the zod twin (`z.input` / `z.infer`), the two faces leave together or neither does: a zod container that would keep
106+
the key in silence holds the TS member too, and an open TS container holds the zod twin. That test's fourth
107+
direction, `MirroredUndeclaredKeys` (a key the zod side mirrors and the TS side does not declare), is the pin that
108+
turns a one-sided deletion red — ⛔ never grow its ledger to land one.
95109

96110
A tombstone held by D3 is listed by the batch with the container that holds it; it is not due, and it is not a
97111
defect of this rule.
@@ -116,7 +130,7 @@ defect of this rule.
116130

117131
## Applying the rule
118132

119-
The first application batch — one card per protocol major thereafter — finds due tombstones mechanically and reports
133+
The first application batch — one card per major thereafter — finds due tombstones mechanically and reports
120134
what it could not date. ⛔ No count is written here: the population moves, and the instrument is the reading
121135
(ui/AGENTS.md #9).
122136

@@ -129,9 +143,9 @@ what it could not date. ⛔ No count is written here: the population moves, and
129143
`objectstack-ai/objectui`, whose `@object-ui/types@*` tags carry that era. On a shallow clone the reading is refused,
130144
⛔ never read as "no history" (`git rev-parse --is-shallow-repository`, with a control leg of known age). For a
131145
tombstone that names a spec retirement, read R from the spec (D2), not from the ui commit.
132-
3. **Judge.** Due ⇔ the current protocol major ≥ R + 2 and D3 does not hold the key. Report three lists — due, held by
146+
3. **Judge.** Due ⇔ the current major (D1) ≥ R + 2 and D3 does not hold the key. Report three lists — due, held by
133147
D3 (with the container), undated — and remove only the first.
134-
4. **Remove and re-pin.** Delete the member and its JSDoc; delete the zod twin where D3 allows; move any test that pins
148+
4. **Remove and re-pin.** Delete the member, its JSDoc and its zod twin together (D3); move any test that pins
135149
the tombstone's named refusal to assert what the key meets now — the TS excess-property refusal and the strict zod
136150
face's unknown-key refusal — ⛔ never delete such a pin without its replacement. Ship the D4 changeset.
137151

0 commit comments

Comments
 (0)