Skip to content

Commit 82ffd67

Browse files
committed
docs(adr): ADR-0087's artifact window names its third disjunct, the major-step replay
The 2026-09-13 addendum stated the artifact-ingestion window as two disjuncts. Since 58f5c90 the door opens a third: a floor of M.0.0 at or above the runtime label replays every retired entry of the conversion step into M, default flips still refused by id first, under the verdict 'converted-major-step'. One dated amendment note under the window bullet records it with the M2 caveat and aligns the retiredAfter definition with the stable-release stamp rule of 719183f; the closing "within-line retirements" sentence is annotated, not rewritten; the status line gains its Amended entry. Claude-Session: https://claude.ai/code/session_012dGTuJen61ECNHXYXKChoC Co-authored-by: Claude <noreply@anthropic.com>
1 parent 02bec6c commit 82ffd67

1 file changed

Lines changed: 42 additions & 1 deletion

File tree

‎docs/adr/0087-metadata-protocol-upgrade-contract.md‎

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0087: Metadata protocol upgrades for AI consumers — conversion over notification, executable migrations, machine-verifiable upgrades
22

3-
**Status**: Accepted (2026-07-04, #2582) · trued up to as-built 2026-07-15 (see Addendum) · **Amended** (2026-09-30, #20390 ruling A — the artifact-ingestion window decides per entry by each retired conversion's `retiredAfter`; see the amendment note under the 2026-09-13 addendum's window bullet) · **Amended** (2026-10-09, #22449 ruling B′ — the two D4 projections, the per-major section of `spec-changes.json` and the protocol upgrade guide, are generated at publish rather than committed; see the amendment note under D4, which says what is generated where and where each part landed, and the struck clauses in the P2 phase-status bullet)
3+
**Status**: Accepted (2026-07-04, #2582) · trued up to as-built 2026-07-15 (see Addendum) · **Amended** (2026-09-30, #20390 ruling A — the artifact-ingestion window decides per entry by each retired conversion's `retiredAfter`; see the amendment note under the 2026-09-13 addendum's window bullet) · **Amended** (2026-10-09, #22449 ruling B′ — the two D4 projections, the per-major section of `spec-changes.json` and the protocol upgrade guide, are generated at publish rather than committed; see the amendment note under D4, which says what is generated where and where each part landed, and the struck clauses in the P2 phase-status bullet) · **Amended** (2026-10-13, #23038 — the artifact-ingestion window's third disjunct, landed as `58f5c90999`: a floor that is the first release of a major replays that major's conversion step, verdict `'converted-major-step'`; see the 2026-10-13 amendment note under the 2026-09-13 addendum's window bullet)
44
**Deciders**: ObjectStack Protocol Architects
55
**Builds on**: [ADR-0059](./0059-third-party-backward-compatibility-gates.md) (layered backward-compat gates — this ADR is its consumer-facing sequel), [ADR-0078](./0078-no-silently-inert-metadata.md) (no declarable-but-unenforced metadata — the un-checked `engines.protocol` is exactly this class), [ADR-0025](./0025-plugin-package-distribution.md) (§3.2 `engines.protocol` / `engines.platform` compatibility ranges, §3.10 #3 protocol-first check order), [ADR-0033](./0033-ai-assisted-metadata-authoring.md) (the authoring population this ADR designs for), [ADR-0049](./0049-no-unenforced-security-properties.md) (enforce-or-remove), [ADR-0054](./0054-runtime-proof-for-authorable-surface.md) (prove-it-runs), AGENTS.md Prime Directive #12 (contract-first, no consumer-side dialect fallbacks — §"Why the conversion layer does not violate PD #12" draws the line)
66
**Consumers**: `@objectstack/spec` (protocol version constant, conversion layer, deprecation/change registries), `@objectstack/cli` (`validate`, `doctor`, `migrate meta`), the runtime metadata loader (handshake + conversion), `@objectstack/mcp` (the AI-native change/migration surface), `@objectstack/create-objectstack`, the Release workflow, and every third-party consumer — whose maintainer is assumed to be an **AI agent**
@@ -1167,6 +1167,39 @@ unconditional strip would start deleting legal metadata the day the keys return.
11671167
> refused it outright. The module docblock of
11681168
> `packages/metadata-core/src/artifact-forward-conversion.ts` states the rule
11691169
> and is its authority.
1170+
1171+
> **Amended (2026-10-13) — a first-release-of-a-major floor replays that
1172+
> major's step.** Provenance: #23001, landed as `58f5c90999` (PR #23023),
1173+
> recorded here by #23038. The window has a THIRD disjunct: the door replays
1174+
> an entry E when `floor < runtime` OR `floor <= E.retiredAfter` OR
1175+
> (`floor` is `M.0.0` AND `E.toMajor` is M). A floor that is the first
1176+
> release of major M, at or above the runtime label, replays every retired
1177+
> entry of the conversion step into M (`CONVERSIONS_BY_MAJOR[M]`, the step
1178+
> `objectstack migrate meta --from M-1` replays), with the default flips
1179+
> still refused by id first, under the verdict `'converted-major-step'`
1180+
> (`packages/metadata-core/src/artifact-forward-conversion.ts#idsTheFloorPostdates`).
1181+
> Why: `M.0.0` is what a bare `^M` range reads as and the floor every build
1182+
> from M's prerelease declares, while the package label still names the
1183+
> last release of M-1 and every retirement landing in the prerelease is
1184+
> stamped at or below that label; so the per-entry half read a `^18`
1185+
> artifact built before a prerelease retirement as postdating every stamp
1186+
> (`'authored-current'`), and the strict parse refused it. The replay is
1187+
> safe because a conversion rewrites only the shape it positively
1188+
> recognizes: an artifact built after a retirement comes back unchanged,
1189+
> and one built before it boots. ⚠️ The M2 caveat: an entry whose old shape
1190+
> becomes legal again cannot be protected for a bare-major floor by this
1191+
> half, nor by the label half, which strips it for the same artifact one
1192+
> release later; the change that re-legalizes a retired key decides that
1193+
> entry's fate at this door, where the default-flip refusal
1194+
> (`packages/metadata-core/src/artifact-forward-conversion.ts#DEFAULT_FLIPS_NOT_REPLAYED_HERE`)
1195+
> is the existing mechanism. The `retiredAfter` the note above defines is a
1196+
> stable `x.y.z`: under a prerelease label (Changesets pre mode) it is the
1197+
> newest published stable release, never a prerelease tarball (#23042,
1198+
> landed as `719183f50`, PR #23130). The addendum's closing sentence,
1199+
> "within-line retirements (17.1 → 17.2)", still names the case that
1200+
> created the window and is annotated there, not rewritten: the window now
1201+
> also serves a prerelease-major floor. The module docblock remains the
1202+
> rule's authority.
11701203
- **⚠️ The shipped key is the DECLARED FLOOR, not the authored version.** The
11711204
ruling says "authored `specVersion`"; what an artifact manifest actually
11721205
carries is a protocol *range*, so the implementation keys off that range's
@@ -1216,3 +1249,11 @@ should CONSUME this policy rather than reimplement it. Nothing here changes the
12161249
protocol handshake's jurisdiction either — a cross-major gap refuses at
12171250
`checkProtocolCompat` before conversion could matter, and this window is for
12181251
within-line retirements (17.1 → 17.2), which are the case that created it.
1252+
1253+
> **Annotated (2026-10-13).** Since `58f5c90999` the window also serves a
1254+
> prerelease-major floor: `^18` on a runtime labelled from 17.7.0 through
1255+
> 18.0.0, and from 18.0.1 on the label half replays the whole chain for it
1256+
> (the 2026-10-13 amendment note under the window bullet). The handshake
1257+
> still answers first: the protocol constant moves to M when M's prerelease
1258+
> opens, so a `^M` floor passes it while the package label still names the
1259+
> last release of M-1, and only then does the window decide.

0 commit comments

Comments
 (0)