From 11b3e6870aa86ce5cb7cb06c8f6830deb8dc8df4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 12:33:37 +0000 Subject: [PATCH 1/4] =?UTF-8?q?docs(adr):=20ADR-0145=20=E2=80=94=20ui=20`?= =?UTF-8?q?=3F:=20never`=20tombstones=20retire=20on=20the=20spec's=20one-m?= =?UTF-8?q?ajor=20window?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records the maintainer's letter A on #25912 (ruling 6407810703): a `?: never` tombstone on the ui authoring face leaves in the first release after the spec major in which its ADR-0087 conversion expires, one major after the retirement it names and never longer. States how R is read (spec-named, pre-ADR-0087 and ui-only retirements), the container carve-out that keeps a deletion from turning a refusal into a silent accept, the minor + Breaking changeset, and how the first application batch finds due tombstones. Declares only; removes nothing. Regenerates the ADR index. Claude-Session: https://claude.ai/code/session_01JQwxbYznNtcYGX8apHQPtk Co-authored-by: Claude --- ...on-tombstones-retire-on-the-spec-window.md | 153 ++++++++++++++++++ docs/adr/README.md | 7 +- 2 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md diff --git a/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md b/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md new file mode 100644 index 00000000000..e529f44ebcc --- /dev/null +++ b/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md @@ -0,0 +1,153 @@ +# 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 + +**Status**: Proposed (2026-10-28). `docs/adr/**` is Tier H (Prime Directive #14): this record becomes **Accepted** by +the maintainer's merge. **This record declares; it implements nothing** — no tombstone is removed, no code, no schema, +no changeset. The first application batch is its own card, cut after the merge (see [Applying the rule](#applying-the-rule)). +**Decided by**: the maintainer's letter **A** of 2026-10-25T01:46Z on +[#25912](https://github.com/objectstack-ai/objectstack/issues/25912) (filed as objectui#13467), transcribed as ruling +[`6407810703`](https://github.com/objectstack-ai/objectstack/issues/25912#issuecomment-6407810703) over the seat's +options [`6407810621`](https://github.com/objectstack-ai/objectstack/issues/25912#issuecomment-6407810621). Executed +here as written and ⛔ not re-opened. Not taken: **B** (a tombstone leaves the day the spec retires its key — the remedy +would die before the authors on the previous major stop needing it) and **C** (no rule — the face only grows). +**Builds on**: [ADR-0087](./0087-metadata-protocol-upgrade-contract.md) (D2's one-major conversion window and its +2026-10-26 addendum, "retirement kits age out after one major" — the window this record reads, ⛔ never restates or +changes), [ADR-0049](./0049-no-unenforced-security-properties.md) (the retirement-tombstone convention the ui mirrors +spell through `retirementTombstone`), AGENTS.md Prime Directive #12 (a deletion that turns a refusal into a silent +accept is the defect it names) and Post-Task Checklist step 3 (the changeset). +**Governs**: every `?: never` member on the ui TypeScript authoring face — `ui/packages/types` and any `.d.ts` another +`ui/packages/*` package publishes — and the zod twin that mirrors it. ⛔ Not a live `@deprecated` member (an alias the +runtime still accepts): it is not a tombstone, and its retirement is its own card's. +**Registry**: the root `docs/adr/` (Prime Directive #13: one registry per repository). `ui/docs/adr/` is the imported +ObjectUI sequence, kept in place, and gains no record here. + +--- + +## Context + +The ui authoring face refuses a retired key **by name**. `BaseSchema` declares no index signature (ui/AGENTS.md #0.1), +so a fresh node literal refuses any key no declaration names; a retired key is kept declared as `key?: never` with a +`/** @deprecated RETIRED (…) … */` JSDoc whose text is the remedy, and the zod mirror declares the same key through +`retirementTombstone` (or a sibling helper in `zod/tombstone.zod.ts`), so a document that arrives as data is refused at +the key's own path with the same remedy. That is #0.1 working as designed. + +What was missing is the exit. Nothing said when a tombstone leaves, so every retirement became a permanent member of +the published `.d.ts`: an AI author reading the face sees the refused keys beside the live ones, and the longer that +list, the more the face teaches what not to write instead of what to write. + +The spec already answers the same question for its own face. ADR-0087 D2 applies each conversion for one major, and +its 2026-10-26 addendum carries the retirement kit — the `retiredKey()` tombstone with its conversion — through the +protocol major after the one it retired in, removing it when the next major is cut; after that the spec's strict +container refuses the key as an unknown one and the prescription lives in the protocol upgrade guide, the CHANGELOG +and git. The same addendum keeps a tombstone whose container **strips** unknown keys, because deleting it would turn a +refusal into a silent strip. The ui tombstone mirrors the spec's; this record reads that window on the ui face. + +## Decision + +### D1 — The rule + +A `?: never` tombstone leaves the ui authoring face in the first release that follows the spec major in which the +retirement it names has its ADR-0087 conversion expire: **one major after the retirement**, the window the spec itself +runs, ⛔ never a longer one. Its remedy JSDoc ships for exactly that window. + +Spelled on ADR-0087's clock, with **R** the protocol major the retirement belongs to (D2 says how R is read): + +- the tombstone is carried through major **R + 1**, and is **due** from the cut of major **R + 2** — the same cut at + which the spec's kit for a key retired in R leaves (ADR-0087, 2026-10-26 addendum); +- "major" is the **protocol** major (`PROTOCOL_VERSION`, the `CURRENT_MAJOR` the spec's deletion gate reads), ⛔ never + the npm major: ADR-0087's pre-launch section keeps the two apart, and the `@object-ui/*` and `@objectstack/*` + packages release as one fixed group, so one clock serves both faces; +- the window binds in both directions: a tombstone that names a spec retirement ⛔ never leaves while the spec still + carries that key's kit (the ui author would lose the remedy the spec still prints), and ⛔ never outlives it. + +### D2 — Reading R, including a retirement that predates ADR-0087 + +- **The tombstone names a spec retirement** (its JSDoc or zod guidance names the `@objectstack/spec` key or release it + mirrors): R is the spec major that retired the key — the `RETIRED_KEYS_BY_MAJOR` row where one exists, otherwise the + spec release clock ADR-0087's deletion gate uses (a key already `[RETIRED]` on the authorable surface at + `@objectstack/spec@.0.0` retired in a major below M). +- **The spec retirement predates ADR-0087**, so no conversion was ever registered for it — the pre-launch one-step + breaks of majors 12–15 that ADR-0087's "Ratified: the pre-launch launch-window exemption" section records, or a + retirement older than D2 itself: it is counted **as if its conversion had run the one-major window from the major it + retired in**. R is the major of the first spec release that refused the key. No such tombstone is granted a fresh + window by this record: with the protocol at major 18, any whose R is 16 or below is already due, and the first + application batch takes it. +- **The tombstone names a ui-only retirement** (an objectui or ui card, no spec key behind it): the same window runs + from the ui's own retirement. R is the major of the first `@object-ui/types` release tag (or the first release of the + package that publishes the `.d.ts`) whose tree carries the tombstone. +- **Where R cannot be read** (no tag, no row, a history the reader cannot reach): the tombstone is not due. The batch + reports it as undated, ⛔ never guesses a date — a wrong date removes a remedy early, which is option B by accident. + +### D3 — A deletion must leave a refusal: the container carve-out + +The rule removes a **prescription**, never a **refusal**. ADR-0087's addendum keeps a spec tombstone whose container +strips unknown keys; the same test applies on both ui faces, judged per key: + +- **TS face.** The `?: never` member leaves only where its container still refuses the key once it is gone — a type + that declares no index signature, directly or through what it extends or intersects. Where the container is open, the + member stays until the container is closed; closing it is its own card. +- **Zod face.** The zod twin leaves in the same release only where every zod face that judges authored metadata still + refuses the key once it is gone. `BaseSchemaCore` ends `.passthrough()`, so a member deleted from a container derived + from it is **kept in silence** by the tolerant face (the case `agedOutSpecKeyRefusal` in `zod/tombstone.zod.ts` + exists for: the ui keeps its own named refusal once the spec's tombstone ages out). Such a twin stays, as the ui's + refusal, until its container refuses unknown keys — the posture the spec's strip-container tombstones keep. +- **Derived faces move together.** Where the TS member is derived from the zod twin (`z.input` / `z.infer`), the two + leave together or neither does. + +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 +defect of this rule. + +### D4 — The changeset that drops a tombstone + +- Level **`minor`**, ⛔ never `major` (`scripts/check-changeset-no-major.mjs` is the authority), on every package whose + published face loses the member. +- A **Breaking** note naming each removed key as `Type.key` and its remedy — the text of the JSDoc being deleted, + carried over as the FROM → TO line — because from that release on the CHANGELOG is where an author who still writes + the key finds what to write instead. +- The declaration lines and the ADR-0087 disposition marker the changeset gates require (AGENTS.md Post-Task Checklist + step 3); ⛔ their vocabulary is not restated here — the gates print it when they fail. + +### D5 — Not in this record + +- **No sweep.** This record removes nothing; the first application batch is its own card. +- **The AGENTS.md pointer.** The ruling puts a one-line pointer to this record in ui/AGENTS.md #0.1 on the AGENTS.md + pull request of [#25919](https://github.com/objectstack-ai/objectstack/issues/25919), not here. +- **No new gate.** The spec's deletion gate dates its own `[RETIRED]` lines; whether the ui face gets a twin of that + clock is a later card's question, decided by measurement, ⛔ not by this record. + +## Applying the rule + +The first application batch — one card per protocol major thereafter — finds due tombstones mechanically and reports +what it could not date. ⛔ No count is written here: the population moves, and the instrument is the reading +(ui/AGENTS.md #9). + +1. **Enumerate.** Every `?: never` member under `ui/packages/*/src`, tests excluded + (`git grep -nE '\?: never' -- 'ui/packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.ts' ':!**/*.test.tsx'`), and, + per member, its zod twin on the mirrored schema, whatever helper of `zod/tombstone.zod.ts` spells it. +2. **Date.** Per member, find the commit that introduced the tombstone with `git log -S'' --reverse` + on a **full** clone, then the first release tag that contains it (`git tag --contains`). The ui tree's history before + the ui merge was not imported into this repository: a tombstone older than the merge is dated on a full clone of + `objectstack-ai/objectui`, whose `@object-ui/types@*` tags carry that era. On a shallow clone the reading is refused, + ⛔ never read as "no history" (`git rev-parse --is-shallow-repository`, with a control leg of known age). For a + tombstone that names a spec retirement, read R from the spec (D2), not from the ui commit. +3. **Judge.** Due ⇔ the current protocol major ≥ R + 2 and D3 does not hold the key. Report three lists — due, held by + D3 (with the container), undated — and remove only the first. +4. **Remove and re-pin.** Delete the member and its JSDoc; delete the zod twin where D3 allows; move any test that pins + the tombstone's named refusal to assert what the key meets now — the TS excess-property refusal and the strict zod + face's unknown-key refusal — ⛔ never delete such a pin without its replacement. Ship the D4 changeset. + +## Consequences + +- The ui authoring face stops only growing: each major sheds the tombstones whose window closed, in step with the + spec's own face, and the two faces never disagree about how long a remedy is printed. +- An author who writes a key after its window meets a bare unknown-key refusal on the TS face, without a remedy; the + remedy lives in the CHANGELOG entry the D4 Breaking note writes. That is the trade ADR-0087 already made for the spec. +- D3 keeps some tombstones past their window, by design: a named refusal outranks a shorter face. Each one held is a + pointer at a container that is still open. + +## Alternatives considered + +- **B — leave immediately when the spec retires the key.** Rejected by the ruling: the remedy disappears while authors + on the previous major still need it. +- **C — no rule.** Rejected by the ruling: every retirement is a permanent obligation on the published face. +- **A longer ui window than the spec's.** Excluded by the ruling's "never a longer one": the faces would print a remedy + for a key the spec no longer prescribes. diff --git a/docs/adr/README.md b/docs/adr/README.md index 6ba5ad87a4a..7f64cc3cd21 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -13,12 +13,12 @@ ## Summary -144 records under `docs/adr/` (139 numbers), 11 under `ui/docs/adr/` (listed separately below). +145 records under `docs/adr/` (140 numbers), 11 under `ui/docs/adr/` (listed separately below). | Status | Records | |---|---| | Accepted | 126 | -| Proposed | 4 | +| Proposed | 5 | | Superseded | 4 | | Draft | 3 | | Deferred | 2 | @@ -26,7 +26,7 @@ | Amended | 1 | | Partially implemented | 1 | | Revised | 1 | -| **Total** | **144** | +| **Total** | **145** | ## Shared numbers @@ -211,6 +211,7 @@ A bare `ADR-0006` names the decision; its current text is [0006-project-environm | [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 | — | — | | [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 | — | — | | [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 | — | — | +| [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 | — | — | ## ObjectUI registry — `ui/docs/adr/` (`ui-` prefix) From 98dcc57e8e6f583cd980c280984578b4d9354148 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 12:54:59 +0000 Subject: [PATCH 2/4] chore(adr): open-head step, take ADR-0145 back out for pr_create The pull request opens at a head whose net diff touches no governed path, so the relay's pr_create requests no Tier H approver before the contract review (#26617). The next commit re-applies 11b3e687 unchanged. Claude-Session: https://claude.ai/code/session_01JQwxbYznNtcYGX8apHQPtk Co-authored-by: Claude --- ...on-tombstones-retire-on-the-spec-window.md | 153 ------------------ docs/adr/README.md | 7 +- 2 files changed, 3 insertions(+), 157 deletions(-) delete mode 100644 docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md diff --git a/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md b/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md deleted file mode 100644 index e529f44ebcc..00000000000 --- a/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md +++ /dev/null @@ -1,153 +0,0 @@ -# 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 - -**Status**: Proposed (2026-10-28). `docs/adr/**` is Tier H (Prime Directive #14): this record becomes **Accepted** by -the maintainer's merge. **This record declares; it implements nothing** — no tombstone is removed, no code, no schema, -no changeset. The first application batch is its own card, cut after the merge (see [Applying the rule](#applying-the-rule)). -**Decided by**: the maintainer's letter **A** of 2026-10-25T01:46Z on -[#25912](https://github.com/objectstack-ai/objectstack/issues/25912) (filed as objectui#13467), transcribed as ruling -[`6407810703`](https://github.com/objectstack-ai/objectstack/issues/25912#issuecomment-6407810703) over the seat's -options [`6407810621`](https://github.com/objectstack-ai/objectstack/issues/25912#issuecomment-6407810621). Executed -here as written and ⛔ not re-opened. Not taken: **B** (a tombstone leaves the day the spec retires its key — the remedy -would die before the authors on the previous major stop needing it) and **C** (no rule — the face only grows). -**Builds on**: [ADR-0087](./0087-metadata-protocol-upgrade-contract.md) (D2's one-major conversion window and its -2026-10-26 addendum, "retirement kits age out after one major" — the window this record reads, ⛔ never restates or -changes), [ADR-0049](./0049-no-unenforced-security-properties.md) (the retirement-tombstone convention the ui mirrors -spell through `retirementTombstone`), AGENTS.md Prime Directive #12 (a deletion that turns a refusal into a silent -accept is the defect it names) and Post-Task Checklist step 3 (the changeset). -**Governs**: every `?: never` member on the ui TypeScript authoring face — `ui/packages/types` and any `.d.ts` another -`ui/packages/*` package publishes — and the zod twin that mirrors it. ⛔ Not a live `@deprecated` member (an alias the -runtime still accepts): it is not a tombstone, and its retirement is its own card's. -**Registry**: the root `docs/adr/` (Prime Directive #13: one registry per repository). `ui/docs/adr/` is the imported -ObjectUI sequence, kept in place, and gains no record here. - ---- - -## Context - -The ui authoring face refuses a retired key **by name**. `BaseSchema` declares no index signature (ui/AGENTS.md #0.1), -so a fresh node literal refuses any key no declaration names; a retired key is kept declared as `key?: never` with a -`/** @deprecated RETIRED (…) … */` JSDoc whose text is the remedy, and the zod mirror declares the same key through -`retirementTombstone` (or a sibling helper in `zod/tombstone.zod.ts`), so a document that arrives as data is refused at -the key's own path with the same remedy. That is #0.1 working as designed. - -What was missing is the exit. Nothing said when a tombstone leaves, so every retirement became a permanent member of -the published `.d.ts`: an AI author reading the face sees the refused keys beside the live ones, and the longer that -list, the more the face teaches what not to write instead of what to write. - -The spec already answers the same question for its own face. ADR-0087 D2 applies each conversion for one major, and -its 2026-10-26 addendum carries the retirement kit — the `retiredKey()` tombstone with its conversion — through the -protocol major after the one it retired in, removing it when the next major is cut; after that the spec's strict -container refuses the key as an unknown one and the prescription lives in the protocol upgrade guide, the CHANGELOG -and git. The same addendum keeps a tombstone whose container **strips** unknown keys, because deleting it would turn a -refusal into a silent strip. The ui tombstone mirrors the spec's; this record reads that window on the ui face. - -## Decision - -### D1 — The rule - -A `?: never` tombstone leaves the ui authoring face in the first release that follows the spec major in which the -retirement it names has its ADR-0087 conversion expire: **one major after the retirement**, the window the spec itself -runs, ⛔ never a longer one. Its remedy JSDoc ships for exactly that window. - -Spelled on ADR-0087's clock, with **R** the protocol major the retirement belongs to (D2 says how R is read): - -- the tombstone is carried through major **R + 1**, and is **due** from the cut of major **R + 2** — the same cut at - which the spec's kit for a key retired in R leaves (ADR-0087, 2026-10-26 addendum); -- "major" is the **protocol** major (`PROTOCOL_VERSION`, the `CURRENT_MAJOR` the spec's deletion gate reads), ⛔ never - the npm major: ADR-0087's pre-launch section keeps the two apart, and the `@object-ui/*` and `@objectstack/*` - packages release as one fixed group, so one clock serves both faces; -- the window binds in both directions: a tombstone that names a spec retirement ⛔ never leaves while the spec still - carries that key's kit (the ui author would lose the remedy the spec still prints), and ⛔ never outlives it. - -### D2 — Reading R, including a retirement that predates ADR-0087 - -- **The tombstone names a spec retirement** (its JSDoc or zod guidance names the `@objectstack/spec` key or release it - mirrors): R is the spec major that retired the key — the `RETIRED_KEYS_BY_MAJOR` row where one exists, otherwise the - spec release clock ADR-0087's deletion gate uses (a key already `[RETIRED]` on the authorable surface at - `@objectstack/spec@.0.0` retired in a major below M). -- **The spec retirement predates ADR-0087**, so no conversion was ever registered for it — the pre-launch one-step - breaks of majors 12–15 that ADR-0087's "Ratified: the pre-launch launch-window exemption" section records, or a - retirement older than D2 itself: it is counted **as if its conversion had run the one-major window from the major it - retired in**. R is the major of the first spec release that refused the key. No such tombstone is granted a fresh - window by this record: with the protocol at major 18, any whose R is 16 or below is already due, and the first - application batch takes it. -- **The tombstone names a ui-only retirement** (an objectui or ui card, no spec key behind it): the same window runs - from the ui's own retirement. R is the major of the first `@object-ui/types` release tag (or the first release of the - package that publishes the `.d.ts`) whose tree carries the tombstone. -- **Where R cannot be read** (no tag, no row, a history the reader cannot reach): the tombstone is not due. The batch - reports it as undated, ⛔ never guesses a date — a wrong date removes a remedy early, which is option B by accident. - -### D3 — A deletion must leave a refusal: the container carve-out - -The rule removes a **prescription**, never a **refusal**. ADR-0087's addendum keeps a spec tombstone whose container -strips unknown keys; the same test applies on both ui faces, judged per key: - -- **TS face.** The `?: never` member leaves only where its container still refuses the key once it is gone — a type - that declares no index signature, directly or through what it extends or intersects. Where the container is open, the - member stays until the container is closed; closing it is its own card. -- **Zod face.** The zod twin leaves in the same release only where every zod face that judges authored metadata still - refuses the key once it is gone. `BaseSchemaCore` ends `.passthrough()`, so a member deleted from a container derived - from it is **kept in silence** by the tolerant face (the case `agedOutSpecKeyRefusal` in `zod/tombstone.zod.ts` - exists for: the ui keeps its own named refusal once the spec's tombstone ages out). Such a twin stays, as the ui's - refusal, until its container refuses unknown keys — the posture the spec's strip-container tombstones keep. -- **Derived faces move together.** Where the TS member is derived from the zod twin (`z.input` / `z.infer`), the two - leave together or neither does. - -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 -defect of this rule. - -### D4 — The changeset that drops a tombstone - -- Level **`minor`**, ⛔ never `major` (`scripts/check-changeset-no-major.mjs` is the authority), on every package whose - published face loses the member. -- A **Breaking** note naming each removed key as `Type.key` and its remedy — the text of the JSDoc being deleted, - carried over as the FROM → TO line — because from that release on the CHANGELOG is where an author who still writes - the key finds what to write instead. -- The declaration lines and the ADR-0087 disposition marker the changeset gates require (AGENTS.md Post-Task Checklist - step 3); ⛔ their vocabulary is not restated here — the gates print it when they fail. - -### D5 — Not in this record - -- **No sweep.** This record removes nothing; the first application batch is its own card. -- **The AGENTS.md pointer.** The ruling puts a one-line pointer to this record in ui/AGENTS.md #0.1 on the AGENTS.md - pull request of [#25919](https://github.com/objectstack-ai/objectstack/issues/25919), not here. -- **No new gate.** The spec's deletion gate dates its own `[RETIRED]` lines; whether the ui face gets a twin of that - clock is a later card's question, decided by measurement, ⛔ not by this record. - -## Applying the rule - -The first application batch — one card per protocol major thereafter — finds due tombstones mechanically and reports -what it could not date. ⛔ No count is written here: the population moves, and the instrument is the reading -(ui/AGENTS.md #9). - -1. **Enumerate.** Every `?: never` member under `ui/packages/*/src`, tests excluded - (`git grep -nE '\?: never' -- 'ui/packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.ts' ':!**/*.test.tsx'`), and, - per member, its zod twin on the mirrored schema, whatever helper of `zod/tombstone.zod.ts` spells it. -2. **Date.** Per member, find the commit that introduced the tombstone with `git log -S'' --reverse` - on a **full** clone, then the first release tag that contains it (`git tag --contains`). The ui tree's history before - the ui merge was not imported into this repository: a tombstone older than the merge is dated on a full clone of - `objectstack-ai/objectui`, whose `@object-ui/types@*` tags carry that era. On a shallow clone the reading is refused, - ⛔ never read as "no history" (`git rev-parse --is-shallow-repository`, with a control leg of known age). For a - tombstone that names a spec retirement, read R from the spec (D2), not from the ui commit. -3. **Judge.** Due ⇔ the current protocol major ≥ R + 2 and D3 does not hold the key. Report three lists — due, held by - D3 (with the container), undated — and remove only the first. -4. **Remove and re-pin.** Delete the member and its JSDoc; delete the zod twin where D3 allows; move any test that pins - the tombstone's named refusal to assert what the key meets now — the TS excess-property refusal and the strict zod - face's unknown-key refusal — ⛔ never delete such a pin without its replacement. Ship the D4 changeset. - -## Consequences - -- The ui authoring face stops only growing: each major sheds the tombstones whose window closed, in step with the - spec's own face, and the two faces never disagree about how long a remedy is printed. -- An author who writes a key after its window meets a bare unknown-key refusal on the TS face, without a remedy; the - remedy lives in the CHANGELOG entry the D4 Breaking note writes. That is the trade ADR-0087 already made for the spec. -- D3 keeps some tombstones past their window, by design: a named refusal outranks a shorter face. Each one held is a - pointer at a container that is still open. - -## Alternatives considered - -- **B — leave immediately when the spec retires the key.** Rejected by the ruling: the remedy disappears while authors - on the previous major still need it. -- **C — no rule.** Rejected by the ruling: every retirement is a permanent obligation on the published face. -- **A longer ui window than the spec's.** Excluded by the ruling's "never a longer one": the faces would print a remedy - for a key the spec no longer prescribes. diff --git a/docs/adr/README.md b/docs/adr/README.md index 7f64cc3cd21..6ba5ad87a4a 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -13,12 +13,12 @@ ## Summary -145 records under `docs/adr/` (140 numbers), 11 under `ui/docs/adr/` (listed separately below). +144 records under `docs/adr/` (139 numbers), 11 under `ui/docs/adr/` (listed separately below). | Status | Records | |---|---| | Accepted | 126 | -| Proposed | 5 | +| Proposed | 4 | | Superseded | 4 | | Draft | 3 | | Deferred | 2 | @@ -26,7 +26,7 @@ | Amended | 1 | | Partially implemented | 1 | | Revised | 1 | -| **Total** | **145** | +| **Total** | **144** | ## Shared numbers @@ -211,7 +211,6 @@ A bare `ADR-0006` names the decision; its current text is [0006-project-environm | [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 | — | — | | [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 | — | — | | [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 | — | — | -| [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 | — | — | ## ObjectUI registry — `ui/docs/adr/` (`ui-` prefix) From 01db912331a529a791ee6091e74fa268fedae959 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 12:55:15 +0000 Subject: [PATCH 3/4] docs(adr): re-apply ADR-0145 after the pull request opened Restores 11b3e687's record and regenerated index unchanged (tree equal to 11b3e687), 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 --- ...on-tombstones-retire-on-the-spec-window.md | 153 ++++++++++++++++++ docs/adr/README.md | 7 +- 2 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md diff --git a/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md b/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md new file mode 100644 index 00000000000..e529f44ebcc --- /dev/null +++ b/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md @@ -0,0 +1,153 @@ +# 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 + +**Status**: Proposed (2026-10-28). `docs/adr/**` is Tier H (Prime Directive #14): this record becomes **Accepted** by +the maintainer's merge. **This record declares; it implements nothing** — no tombstone is removed, no code, no schema, +no changeset. The first application batch is its own card, cut after the merge (see [Applying the rule](#applying-the-rule)). +**Decided by**: the maintainer's letter **A** of 2026-10-25T01:46Z on +[#25912](https://github.com/objectstack-ai/objectstack/issues/25912) (filed as objectui#13467), transcribed as ruling +[`6407810703`](https://github.com/objectstack-ai/objectstack/issues/25912#issuecomment-6407810703) over the seat's +options [`6407810621`](https://github.com/objectstack-ai/objectstack/issues/25912#issuecomment-6407810621). Executed +here as written and ⛔ not re-opened. Not taken: **B** (a tombstone leaves the day the spec retires its key — the remedy +would die before the authors on the previous major stop needing it) and **C** (no rule — the face only grows). +**Builds on**: [ADR-0087](./0087-metadata-protocol-upgrade-contract.md) (D2's one-major conversion window and its +2026-10-26 addendum, "retirement kits age out after one major" — the window this record reads, ⛔ never restates or +changes), [ADR-0049](./0049-no-unenforced-security-properties.md) (the retirement-tombstone convention the ui mirrors +spell through `retirementTombstone`), AGENTS.md Prime Directive #12 (a deletion that turns a refusal into a silent +accept is the defect it names) and Post-Task Checklist step 3 (the changeset). +**Governs**: every `?: never` member on the ui TypeScript authoring face — `ui/packages/types` and any `.d.ts` another +`ui/packages/*` package publishes — and the zod twin that mirrors it. ⛔ Not a live `@deprecated` member (an alias the +runtime still accepts): it is not a tombstone, and its retirement is its own card's. +**Registry**: the root `docs/adr/` (Prime Directive #13: one registry per repository). `ui/docs/adr/` is the imported +ObjectUI sequence, kept in place, and gains no record here. + +--- + +## Context + +The ui authoring face refuses a retired key **by name**. `BaseSchema` declares no index signature (ui/AGENTS.md #0.1), +so a fresh node literal refuses any key no declaration names; a retired key is kept declared as `key?: never` with a +`/** @deprecated RETIRED (…) … */` JSDoc whose text is the remedy, and the zod mirror declares the same key through +`retirementTombstone` (or a sibling helper in `zod/tombstone.zod.ts`), so a document that arrives as data is refused at +the key's own path with the same remedy. That is #0.1 working as designed. + +What was missing is the exit. Nothing said when a tombstone leaves, so every retirement became a permanent member of +the published `.d.ts`: an AI author reading the face sees the refused keys beside the live ones, and the longer that +list, the more the face teaches what not to write instead of what to write. + +The spec already answers the same question for its own face. ADR-0087 D2 applies each conversion for one major, and +its 2026-10-26 addendum carries the retirement kit — the `retiredKey()` tombstone with its conversion — through the +protocol major after the one it retired in, removing it when the next major is cut; after that the spec's strict +container refuses the key as an unknown one and the prescription lives in the protocol upgrade guide, the CHANGELOG +and git. The same addendum keeps a tombstone whose container **strips** unknown keys, because deleting it would turn a +refusal into a silent strip. The ui tombstone mirrors the spec's; this record reads that window on the ui face. + +## Decision + +### D1 — The rule + +A `?: never` tombstone leaves the ui authoring face in the first release that follows the spec major in which the +retirement it names has its ADR-0087 conversion expire: **one major after the retirement**, the window the spec itself +runs, ⛔ never a longer one. Its remedy JSDoc ships for exactly that window. + +Spelled on ADR-0087's clock, with **R** the protocol major the retirement belongs to (D2 says how R is read): + +- the tombstone is carried through major **R + 1**, and is **due** from the cut of major **R + 2** — the same cut at + which the spec's kit for a key retired in R leaves (ADR-0087, 2026-10-26 addendum); +- "major" is the **protocol** major (`PROTOCOL_VERSION`, the `CURRENT_MAJOR` the spec's deletion gate reads), ⛔ never + the npm major: ADR-0087's pre-launch section keeps the two apart, and the `@object-ui/*` and `@objectstack/*` + packages release as one fixed group, so one clock serves both faces; +- the window binds in both directions: a tombstone that names a spec retirement ⛔ never leaves while the spec still + carries that key's kit (the ui author would lose the remedy the spec still prints), and ⛔ never outlives it. + +### D2 — Reading R, including a retirement that predates ADR-0087 + +- **The tombstone names a spec retirement** (its JSDoc or zod guidance names the `@objectstack/spec` key or release it + mirrors): R is the spec major that retired the key — the `RETIRED_KEYS_BY_MAJOR` row where one exists, otherwise the + spec release clock ADR-0087's deletion gate uses (a key already `[RETIRED]` on the authorable surface at + `@objectstack/spec@.0.0` retired in a major below M). +- **The spec retirement predates ADR-0087**, so no conversion was ever registered for it — the pre-launch one-step + breaks of majors 12–15 that ADR-0087's "Ratified: the pre-launch launch-window exemption" section records, or a + retirement older than D2 itself: it is counted **as if its conversion had run the one-major window from the major it + retired in**. R is the major of the first spec release that refused the key. No such tombstone is granted a fresh + window by this record: with the protocol at major 18, any whose R is 16 or below is already due, and the first + application batch takes it. +- **The tombstone names a ui-only retirement** (an objectui or ui card, no spec key behind it): the same window runs + from the ui's own retirement. R is the major of the first `@object-ui/types` release tag (or the first release of the + package that publishes the `.d.ts`) whose tree carries the tombstone. +- **Where R cannot be read** (no tag, no row, a history the reader cannot reach): the tombstone is not due. The batch + reports it as undated, ⛔ never guesses a date — a wrong date removes a remedy early, which is option B by accident. + +### D3 — A deletion must leave a refusal: the container carve-out + +The rule removes a **prescription**, never a **refusal**. ADR-0087's addendum keeps a spec tombstone whose container +strips unknown keys; the same test applies on both ui faces, judged per key: + +- **TS face.** The `?: never` member leaves only where its container still refuses the key once it is gone — a type + that declares no index signature, directly or through what it extends or intersects. Where the container is open, the + member stays until the container is closed; closing it is its own card. +- **Zod face.** The zod twin leaves in the same release only where every zod face that judges authored metadata still + refuses the key once it is gone. `BaseSchemaCore` ends `.passthrough()`, so a member deleted from a container derived + from it is **kept in silence** by the tolerant face (the case `agedOutSpecKeyRefusal` in `zod/tombstone.zod.ts` + exists for: the ui keeps its own named refusal once the spec's tombstone ages out). Such a twin stays, as the ui's + refusal, until its container refuses unknown keys — the posture the spec's strip-container tombstones keep. +- **Derived faces move together.** Where the TS member is derived from the zod twin (`z.input` / `z.infer`), the two + leave together or neither does. + +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 +defect of this rule. + +### D4 — The changeset that drops a tombstone + +- Level **`minor`**, ⛔ never `major` (`scripts/check-changeset-no-major.mjs` is the authority), on every package whose + published face loses the member. +- A **Breaking** note naming each removed key as `Type.key` and its remedy — the text of the JSDoc being deleted, + carried over as the FROM → TO line — because from that release on the CHANGELOG is where an author who still writes + the key finds what to write instead. +- The declaration lines and the ADR-0087 disposition marker the changeset gates require (AGENTS.md Post-Task Checklist + step 3); ⛔ their vocabulary is not restated here — the gates print it when they fail. + +### D5 — Not in this record + +- **No sweep.** This record removes nothing; the first application batch is its own card. +- **The AGENTS.md pointer.** The ruling puts a one-line pointer to this record in ui/AGENTS.md #0.1 on the AGENTS.md + pull request of [#25919](https://github.com/objectstack-ai/objectstack/issues/25919), not here. +- **No new gate.** The spec's deletion gate dates its own `[RETIRED]` lines; whether the ui face gets a twin of that + clock is a later card's question, decided by measurement, ⛔ not by this record. + +## Applying the rule + +The first application batch — one card per protocol major thereafter — finds due tombstones mechanically and reports +what it could not date. ⛔ No count is written here: the population moves, and the instrument is the reading +(ui/AGENTS.md #9). + +1. **Enumerate.** Every `?: never` member under `ui/packages/*/src`, tests excluded + (`git grep -nE '\?: never' -- 'ui/packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.ts' ':!**/*.test.tsx'`), and, + per member, its zod twin on the mirrored schema, whatever helper of `zod/tombstone.zod.ts` spells it. +2. **Date.** Per member, find the commit that introduced the tombstone with `git log -S'' --reverse` + on a **full** clone, then the first release tag that contains it (`git tag --contains`). The ui tree's history before + the ui merge was not imported into this repository: a tombstone older than the merge is dated on a full clone of + `objectstack-ai/objectui`, whose `@object-ui/types@*` tags carry that era. On a shallow clone the reading is refused, + ⛔ never read as "no history" (`git rev-parse --is-shallow-repository`, with a control leg of known age). For a + tombstone that names a spec retirement, read R from the spec (D2), not from the ui commit. +3. **Judge.** Due ⇔ the current protocol major ≥ R + 2 and D3 does not hold the key. Report three lists — due, held by + D3 (with the container), undated — and remove only the first. +4. **Remove and re-pin.** Delete the member and its JSDoc; delete the zod twin where D3 allows; move any test that pins + the tombstone's named refusal to assert what the key meets now — the TS excess-property refusal and the strict zod + face's unknown-key refusal — ⛔ never delete such a pin without its replacement. Ship the D4 changeset. + +## Consequences + +- The ui authoring face stops only growing: each major sheds the tombstones whose window closed, in step with the + spec's own face, and the two faces never disagree about how long a remedy is printed. +- An author who writes a key after its window meets a bare unknown-key refusal on the TS face, without a remedy; the + remedy lives in the CHANGELOG entry the D4 Breaking note writes. That is the trade ADR-0087 already made for the spec. +- D3 keeps some tombstones past their window, by design: a named refusal outranks a shorter face. Each one held is a + pointer at a container that is still open. + +## Alternatives considered + +- **B — leave immediately when the spec retires the key.** Rejected by the ruling: the remedy disappears while authors + on the previous major still need it. +- **C — no rule.** Rejected by the ruling: every retirement is a permanent obligation on the published face. +- **A longer ui window than the spec's.** Excluded by the ruling's "never a longer one": the faces would print a remedy + for a key the spec no longer prescribes. diff --git a/docs/adr/README.md b/docs/adr/README.md index 6ba5ad87a4a..7f64cc3cd21 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -13,12 +13,12 @@ ## Summary -144 records under `docs/adr/` (139 numbers), 11 under `ui/docs/adr/` (listed separately below). +145 records under `docs/adr/` (140 numbers), 11 under `ui/docs/adr/` (listed separately below). | Status | Records | |---|---| | Accepted | 126 | -| Proposed | 4 | +| Proposed | 5 | | Superseded | 4 | | Draft | 3 | | Deferred | 2 | @@ -26,7 +26,7 @@ | Amended | 1 | | Partially implemented | 1 | | Revised | 1 | -| **Total** | **144** | +| **Total** | **145** | ## Shared numbers @@ -211,6 +211,7 @@ A bare `ADR-0006` names the decision; its current text is [0006-project-environm | [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 | — | — | | [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 | — | — | | [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 | — | — | +| [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 | — | — | ## ObjectUI registry — `ui/docs/adr/` (`ui-` prefix) From d3e67407920a67ae48e29a62a7b1c8bb32a50cda Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 13:25:57 +0000 Subject: [PATCH 4/4] =?UTF-8?q?docs(adr):=20ADR-0145=20=E2=80=94=20mirror?= =?UTF-8?q?=20pairs=20retire=20together;=20one=20release=20clock;=20regist?= =?UTF-8?q?ry=20cited?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- ...on-tombstones-retire-on-the-spec-window.md | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md b/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md index e529f44ebcc..41a28980905 100644 --- a/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md +++ b/docs/adr/0145-ui-deprecation-tombstones-retire-on-the-spec-window.md @@ -17,8 +17,11 @@ accept is the defect it names) and Post-Task Checklist step 3 (the changeset). **Governs**: every `?: never` member on the ui TypeScript authoring face — `ui/packages/types` and any `.d.ts` another `ui/packages/*` package publishes — and the zod twin that mirrors it. ⛔ Not a live `@deprecated` member (an alias the runtime still accepts): it is not a tombstone, and its retirement is its own card's. -**Registry**: the root `docs/adr/` (Prime Directive #13: one registry per repository). `ui/docs/adr/` is the imported -ObjectUI sequence, kept in place, and gains no record here. +**Registry**: the root `docs/adr/`. The ruling's words were "objectui `docs/adr/**`", written before the card moved into +this repository. Here `ui/docs/adr/` is the imported ObjectUI sequence, files untouched and indexed under the `ui-` +prefix (#26080, ruling A), and `scripts/check-adr-anchors.mjs` resolves a bare `ADR-NNNN` against the root registry +only, spelling a ui record `ObjectUI ADR-NNNN`; so a new decision governing `ui/` code lands here (Prime Directive #13: +an ADR lives in the repository whose code it governs). --- @@ -53,27 +56,34 @@ Spelled on ADR-0087's clock, with **R** the protocol major the retirement belong - the tombstone is carried through major **R + 1**, and is **due** from the cut of major **R + 2** — the same cut at which the spec's kit for a key retired in R leaves (ADR-0087, 2026-10-26 addendum); -- "major" is the **protocol** major (`PROTOCOL_VERSION`, the `CURRENT_MAJOR` the spec's deletion gate reads), ⛔ never - the npm major: ADR-0087's pre-launch section keeps the two apart, and the `@object-ui/*` and `@objectstack/*` - packages release as one fixed group, so one clock serves both faces; +- "major" is the major of the one `fixed` release group: `@objectstack/*` and `@object-ui/*` release together, lockstep + with `PROTOCOL_VERSION` at every cut, and the spec's deletion gate reads its `CURRENT_MAJOR` off + `@objectstack/spec`'s package version — so one clock serves both faces. A changeset's *level* is a different axis + (ADR-0087's 2026-09-13 amendment, `scripts/check-changeset-no-major.mjs`) and moves nothing here; - the window binds in both directions: a tombstone that names a spec retirement ⛔ never leaves while the spec still - carries that key's kit (the ui author would lose the remedy the spec still prints), and ⛔ never outlives it. + carries that key's kit (the ui author would lose the remedy the spec still prints), and ⛔ never outlives it — except + as D3 holds it: a named refusal D3 keeps outlives the kit by design (the `agedOutSpecKeyRefusal` twins are that case). ### D2 — Reading R, including a retirement that predates ADR-0087 - **The tombstone names a spec retirement** (its JSDoc or zod guidance names the `@objectstack/spec` key or release it mirrors): R is the spec major that retired the key — the `RETIRED_KEYS_BY_MAJOR` row where one exists, otherwise the - spec release clock ADR-0087's deletion gate uses (a key already `[RETIRED]` on the authorable surface at - `@objectstack/spec@.0.0` retired in a major below M). + release-tag clock ADR-0087's deletion gate uses (a key already `[RETIRED]` on the authorable surface at + `@objectstack/spec@.0.0` retired in a major below M). ⚠️ A ui guidance that says a key "was retired in + @objectstack/spec 17.0.0" (`agedOutSpecKeyRefusal`'s `retiredIn`) names the first release that **refused** it; by the + 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 + runs a window one major longer than the spec's, which D1 forbids. - **The spec retirement predates ADR-0087**, so no conversion was ever registered for it — the pre-launch one-step breaks of majors 12–15 that ADR-0087's "Ratified: the pre-launch launch-window exemption" section records, or a retirement older than D2 itself: it is counted **as if its conversion had run the one-major window from the major it - retired in**. R is the major of the first spec release that refused the key. No such tombstone is granted a fresh + retired in**. R is read on the same tag clock: the major of the first spec release that refused the key, where a key + first refused by a major's `.0.0` cut counts in the major before it. No such tombstone is granted a fresh window by this record: with the protocol at major 18, any whose R is 16 or below is already due, and the first application batch takes it. - **The tombstone names a ui-only retirement** (an objectui or ui card, no spec key behind it): the same window runs from the ui's own retirement. R is the major of the first `@object-ui/types` release tag (or the first release of the - package that publishes the `.d.ts`) whose tree carries the tombstone. + package that publishes the `.d.ts`) whose tree carries the tombstone, on the same convention: first carried by a + major's `.0.0` cut counts in the major before it. - **Where R cannot be read** (no tag, no row, a history the reader cannot reach): the tombstone is not due. The batch reports it as undated, ⛔ never guesses a date — a wrong date removes a remedy early, which is option B by accident. @@ -90,8 +100,12 @@ strips unknown keys; the same test applies on both ui faces, judged per key: from it is **kept in silence** by the tolerant face (the case `agedOutSpecKeyRefusal` in `zod/tombstone.zod.ts` exists for: the ui keeps its own named refusal once the spec's tombstone ages out). Such a twin stays, as the ui's refusal, until its container refuses unknown keys — the posture the spec's strip-container tombstones keep. -- **Derived faces move together.** Where the TS member is derived from the zod twin (`z.input` / `z.infer`), the two - leave together or neither does. +- **A mirror pair moves together — the key is held, not one face.** Where the TS type and its zod schema are a + registered mirror pair (`ui/packages/types/src/__tests__/zod-mirror-parity.test.ts`), or the TS member is derived from + the zod twin (`z.input` / `z.infer`), the two faces leave together or neither does: a zod container that would keep + the key in silence holds the TS member too, and an open TS container holds the zod twin. That test's fourth + direction, `MirroredUndeclaredKeys` (a key the zod side mirrors and the TS side does not declare), is the pin that + turns a one-sided deletion red — ⛔ never grow its ledger to land one. 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 defect of this rule. @@ -116,7 +130,7 @@ defect of this rule. ## Applying the rule -The first application batch — one card per protocol major thereafter — finds due tombstones mechanically and reports +The first application batch — one card per major thereafter — finds due tombstones mechanically and reports what it could not date. ⛔ No count is written here: the population moves, and the instrument is the reading (ui/AGENTS.md #9). @@ -129,9 +143,9 @@ what it could not date. ⛔ No count is written here: the population moves, and `objectstack-ai/objectui`, whose `@object-ui/types@*` tags carry that era. On a shallow clone the reading is refused, ⛔ never read as "no history" (`git rev-parse --is-shallow-repository`, with a control leg of known age). For a tombstone that names a spec retirement, read R from the spec (D2), not from the ui commit. -3. **Judge.** Due ⇔ the current protocol major ≥ R + 2 and D3 does not hold the key. Report three lists — due, held by +3. **Judge.** Due ⇔ the current major (D1) ≥ R + 2 and D3 does not hold the key. Report three lists — due, held by D3 (with the container), undated — and remove only the first. -4. **Remove and re-pin.** Delete the member and its JSDoc; delete the zod twin where D3 allows; move any test that pins +4. **Remove and re-pin.** Delete the member, its JSDoc and its zod twin together (D3); move any test that pins the tombstone's named refusal to assert what the key meets now — the TS excess-property refusal and the strict zod face's unknown-key refusal — ⛔ never delete such a pin without its replacement. Ship the D4 changeset.