Skip to content

Commit 01db912

Browse files
committed
docs(adr): re-apply ADR-0145 after the pull request opened
Restores 11b3e68's record and regenerated index unchanged (tree equal to 11b3e68), now that the draft pull request is open with no approver requested; the seat asks for approvers after the contract review PASS and its ACCEPT (#26617). Claude-Session: https://claude.ai/code/session_01JQwxbYznNtcYGX8apHQPtk Co-authored-by: Claude <noreply@anthropic.com>
1 parent 98dcc57 commit 01db912

2 files changed

Lines changed: 157 additions & 3 deletions

File tree

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# ADR-0145: A `?: never` tombstone on the ui authoring face retires on the spec's own window — one major after the retirement it names, never longer
2+
3+
**Status**: Proposed (2026-10-28). `docs/adr/**` is Tier H (Prime Directive #14): this record becomes **Accepted** by
4+
the maintainer's merge. **This record declares; it implements nothing** — no tombstone is removed, no code, no schema,
5+
no changeset. The first application batch is its own card, cut after the merge (see [Applying the rule](#applying-the-rule)).
6+
**Decided by**: the maintainer's letter **A** of 2026-10-25T01:46Z on
7+
[#25912](https://github.com/objectstack-ai/objectstack/issues/25912) (filed as objectui#13467), transcribed as ruling
8+
[`6407810703`](https://github.com/objectstack-ai/objectstack/issues/25912#issuecomment-6407810703) over the seat's
9+
options [`6407810621`](https://github.com/objectstack-ai/objectstack/issues/25912#issuecomment-6407810621). Executed
10+
here as written and ⛔ not re-opened. Not taken: **B** (a tombstone leaves the day the spec retires its key — the remedy
11+
would die before the authors on the previous major stop needing it) and **C** (no rule — the face only grows).
12+
**Builds on**: [ADR-0087](./0087-metadata-protocol-upgrade-contract.md) (D2's one-major conversion window and its
13+
2026-10-26 addendum, "retirement kits age out after one major" — the window this record reads, ⛔ never restates or
14+
changes), [ADR-0049](./0049-no-unenforced-security-properties.md) (the retirement-tombstone convention the ui mirrors
15+
spell through `retirementTombstone`), AGENTS.md Prime Directive #12 (a deletion that turns a refusal into a silent
16+
accept is the defect it names) and Post-Task Checklist step 3 (the changeset).
17+
**Governs**: every `?: never` member on the ui TypeScript authoring face — `ui/packages/types` and any `.d.ts` another
18+
`ui/packages/*` package publishes — and the zod twin that mirrors it. ⛔ Not a live `@deprecated` member (an alias the
19+
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.
22+
23+
---
24+
25+
## Context
26+
27+
The ui authoring face refuses a retired key **by name**. `BaseSchema` declares no index signature (ui/AGENTS.md #0.1),
28+
so a fresh node literal refuses any key no declaration names; a retired key is kept declared as `key?: never` with a
29+
`/** @deprecated RETIRED (…) … */` JSDoc whose text is the remedy, and the zod mirror declares the same key through
30+
`retirementTombstone` (or a sibling helper in `zod/tombstone.zod.ts`), so a document that arrives as data is refused at
31+
the key's own path with the same remedy. That is #0.1 working as designed.
32+
33+
What was missing is the exit. Nothing said when a tombstone leaves, so every retirement became a permanent member of
34+
the published `.d.ts`: an AI author reading the face sees the refused keys beside the live ones, and the longer that
35+
list, the more the face teaches what not to write instead of what to write.
36+
37+
The spec already answers the same question for its own face. ADR-0087 D2 applies each conversion for one major, and
38+
its 2026-10-26 addendum carries the retirement kit — the `retiredKey()` tombstone with its conversion — through the
39+
protocol major after the one it retired in, removing it when the next major is cut; after that the spec's strict
40+
container refuses the key as an unknown one and the prescription lives in the protocol upgrade guide, the CHANGELOG
41+
and git. The same addendum keeps a tombstone whose container **strips** unknown keys, because deleting it would turn a
42+
refusal into a silent strip. The ui tombstone mirrors the spec's; this record reads that window on the ui face.
43+
44+
## Decision
45+
46+
### D1 — The rule
47+
48+
A `?: never` tombstone leaves the ui authoring face in the first release that follows the spec major in which the
49+
retirement it names has its ADR-0087 conversion expire: **one major after the retirement**, the window the spec itself
50+
runs, ⛔ never a longer one. Its remedy JSDoc ships for exactly that window.
51+
52+
Spelled on ADR-0087's clock, with **R** the protocol major the retirement belongs to (D2 says how R is read):
53+
54+
- the tombstone is carried through major **R + 1**, and is **due** from the cut of major **R + 2** — the same cut at
55+
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+
- 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.
61+
62+
### D2 — Reading R, including a retirement that predates ADR-0087
63+
64+
- **The tombstone names a spec retirement** (its JSDoc or zod guidance names the `@objectstack/spec` key or release it
65+
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).
68+
- **The spec retirement predates ADR-0087**, so no conversion was ever registered for it — the pre-launch one-step
69+
breaks of majors 12–15 that ADR-0087's "Ratified: the pre-launch launch-window exemption" section records, or a
70+
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
72+
window by this record: with the protocol at major 18, any whose R is 16 or below is already due, and the first
73+
application batch takes it.
74+
- **The tombstone names a ui-only retirement** (an objectui or ui card, no spec key behind it): the same window runs
75+
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.
77+
- **Where R cannot be read** (no tag, no row, a history the reader cannot reach): the tombstone is not due. The batch
78+
reports it as undated, ⛔ never guesses a date — a wrong date removes a remedy early, which is option B by accident.
79+
80+
### D3 — A deletion must leave a refusal: the container carve-out
81+
82+
The rule removes a **prescription**, never a **refusal**. ADR-0087's addendum keeps a spec tombstone whose container
83+
strips unknown keys; the same test applies on both ui faces, judged per key:
84+
85+
- **TS face.** The `?: never` member leaves only where its container still refuses the key once it is gone — a type
86+
that declares no index signature, directly or through what it extends or intersects. Where the container is open, the
87+
member stays until the container is closed; closing it is its own card.
88+
- **Zod face.** The zod twin leaves in the same release only where every zod face that judges authored metadata still
89+
refuses the key once it is gone. `BaseSchemaCore` ends `.passthrough()`, so a member deleted from a container derived
90+
from it is **kept in silence** by the tolerant face (the case `agedOutSpecKeyRefusal` in `zod/tombstone.zod.ts`
91+
exists for: the ui keeps its own named refusal once the spec's tombstone ages out). Such a twin stays, as the ui's
92+
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.
95+
96+
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
97+
defect of this rule.
98+
99+
### D4 — The changeset that drops a tombstone
100+
101+
- Level **`minor`**, ⛔ never `major` (`scripts/check-changeset-no-major.mjs` is the authority), on every package whose
102+
published face loses the member.
103+
- A **Breaking** note naming each removed key as `Type.key` and its remedy — the text of the JSDoc being deleted,
104+
carried over as the FROM → TO line — because from that release on the CHANGELOG is where an author who still writes
105+
the key finds what to write instead.
106+
- The declaration lines and the ADR-0087 disposition marker the changeset gates require (AGENTS.md Post-Task Checklist
107+
step 3); ⛔ their vocabulary is not restated here — the gates print it when they fail.
108+
109+
### D5 — Not in this record
110+
111+
- **No sweep.** This record removes nothing; the first application batch is its own card.
112+
- **The AGENTS.md pointer.** The ruling puts a one-line pointer to this record in ui/AGENTS.md #0.1 on the AGENTS.md
113+
pull request of [#25919](https://github.com/objectstack-ai/objectstack/issues/25919), not here.
114+
- **No new gate.** The spec's deletion gate dates its own `[RETIRED]` lines; whether the ui face gets a twin of that
115+
clock is a later card's question, decided by measurement, ⛔ not by this record.
116+
117+
## Applying the rule
118+
119+
The first application batch — one card per protocol major thereafter — finds due tombstones mechanically and reports
120+
what it could not date. ⛔ No count is written here: the population moves, and the instrument is the reading
121+
(ui/AGENTS.md #9).
122+
123+
1. **Enumerate.** Every `?: never` member under `ui/packages/*/src`, tests excluded
124+
(`git grep -nE '\?: never' -- 'ui/packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.ts' ':!**/*.test.tsx'`), and,
125+
per member, its zod twin on the mirrored schema, whatever helper of `zod/tombstone.zod.ts` spells it.
126+
2. **Date.** Per member, find the commit that introduced the tombstone with `git log -S'<the member line>' --reverse`
127+
on a **full** clone, then the first release tag that contains it (`git tag --contains`). The ui tree's history before
128+
the ui merge was not imported into this repository: a tombstone older than the merge is dated on a full clone of
129+
`objectstack-ai/objectui`, whose `@object-ui/types@*` tags carry that era. On a shallow clone the reading is refused,
130+
⛔ never read as "no history" (`git rev-parse --is-shallow-repository`, with a control leg of known age). For a
131+
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
133+
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
135+
the tombstone's named refusal to assert what the key meets now — the TS excess-property refusal and the strict zod
136+
face's unknown-key refusal — ⛔ never delete such a pin without its replacement. Ship the D4 changeset.
137+
138+
## Consequences
139+
140+
- The ui authoring face stops only growing: each major sheds the tombstones whose window closed, in step with the
141+
spec's own face, and the two faces never disagree about how long a remedy is printed.
142+
- An author who writes a key after its window meets a bare unknown-key refusal on the TS face, without a remedy; the
143+
remedy lives in the CHANGELOG entry the D4 Breaking note writes. That is the trade ADR-0087 already made for the spec.
144+
- D3 keeps some tombstones past their window, by design: a named refusal outranks a shorter face. Each one held is a
145+
pointer at a container that is still open.
146+
147+
## Alternatives considered
148+
149+
- **B — leave immediately when the spec retires the key.** Rejected by the ruling: the remedy disappears while authors
150+
on the previous major still need it.
151+
- **C — no rule.** Rejected by the ruling: every retirement is a permanent obligation on the published face.
152+
- **A longer ui window than the spec's.** Excluded by the ruling's "never a longer one": the faces would print a remedy
153+
for a key the spec no longer prescribes.

‎docs/adr/README.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@
1313

1414
## Summary
1515

16-
144 records under `docs/adr/` (139 numbers), 11 under `ui/docs/adr/` (listed separately below).
16+
145 records under `docs/adr/` (140 numbers), 11 under `ui/docs/adr/` (listed separately below).
1717

1818
| Status | Records |
1919
|---|---|
2020
| Accepted | 126 |
21-
| Proposed | 4 |
21+
| Proposed | 5 |
2222
| Superseded | 4 |
2323
| Draft | 3 |
2424
| Deferred | 2 |
2525
| Withdrawn | 2 |
2626
| Amended | 1 |
2727
| Partially implemented | 1 |
2828
| Revised | 1 |
29-
| **Total** | **144** |
29+
| **Total** | **145** |
3030

3131
## Shared numbers
3232

@@ -211,6 +211,7 @@ A bare `ADR-0006` names the decision; its current text is [0006-project-environm
211211
| [ADR-0141](./0141-inter-app-dependency-declared-checked-before-install.md) | An app may depend on another installed app — declared in `manifest.dependencies`, checked once before install… | Proposed — 2026-10-25; first draft 2026-10-24. `docs/adr/` is Tier H (Prim… | 2026-10-25 | — | — |
212212
| [ADR-0142](./0142-ownership-queue-membership-claim-and-assignment-rules.md) | An ownership queue is a platform primitive — a queue entity, membership through the existing recipient kinds,… | Proposed — 2026-10-25 · Amended (2026-10-28, ruling `6457475657` on #25769… | 2026-10-25 | — | — |
213213
| [ADR-0143](./0143-outbound-connector-auth-is-open-tier.md) | Outbound connector auth is open tier — OAuth2 grants, vendor app-credential exchange and request signing in o… | Accepted — 2026-10-28. Proposed 2026-10-27 (#26833, which also landed the… | 2026-10-28 | — | — |
214+
| [ADR-0145](./0145-ui-deprecation-tombstones-retire-on-the-spec-window.md) | A `?: never` tombstone on the ui authoring face retires on the spec's own window — one major after the retire… | Proposed — 2026-10-28. `docs/adr/` is Tier H (Prime Directive #14): this r… | 2026-10-28 | — | — |
214215

215216
## ObjectUI registry — `ui/docs/adr/` (`ui-` prefix)
216217

0 commit comments

Comments
 (0)