|
1 | 1 | # ADR-0087: Metadata protocol upgrades for AI consumers — conversion over notification, executable migrations, machine-verifiable upgrades |
2 | 2 |
|
3 | | -**Status**: Proposed (2026-07-04) |
| 3 | +**Status**: Accepted (2026-07-04, #2582) · trued up to as-built 2026-07-15 (see Addendum) |
4 | 4 | **Deciders**: ObjectStack Protocol Architects |
5 | 5 | **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) |
6 | 6 | **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** |
@@ -312,3 +312,72 @@ scaling framework-side execution when the consumers can execute themselves. |
312 | 312 | consumer still gets strictly more than the pre-ADR world (fail-fast, a generated |
313 | 313 | guide, runnable migrations) — the bet has no downside for them, only a smaller |
314 | 314 | upside. |
| 315 | + |
| 316 | +--- |
| 317 | + |
| 318 | +## Addendum (2026-07-15) — as-built true-up |
| 319 | + |
| 320 | +### Phase status |
| 321 | + |
| 322 | +- **P0 — handshake (D1): shipped.** `PROTOCOL_VERSION` + the pure handshake core |
| 323 | + landed in #2650 (install seam). The **load seams** followed: the boot-time |
| 324 | + durable-package rehydration path (`@objectstack/service-package`) refuses an |
| 325 | + incompatible `sys_packages` row with the structured diagnostic and continues |
| 326 | + booting; `AppPlugin` (code-defined stacks) fails fast before the manifest is |
| 327 | + decomposed. The grandfathering ratchet is closed from both ends: |
| 328 | + `objectstack lint` warns on a manifest with no range |
| 329 | + (`protocol/missing-engines-range`), and the `create-objectstack` template |
| 330 | + stamps `engines: { protocol: '^<major>' }` (re-stamped at version time by |
| 331 | + `scripts/sync-template-versions.mjs`). |
| 332 | +- **P1 — conversion layer (D2): shipped** in #2897, seeded with the retroactive |
| 333 | + protocol-11 table; the PD #12 retirement path was proven on the CRUD |
| 334 | + `filters` fallback. |
| 335 | +- **P2 — chain + manifest + guide (D3/D4): shipped.** #2897 landed the chain and |
| 336 | + `composeSpecChanges`; this true-up adds the release side: `spec-changes.json` |
| 337 | + is generated from the registries (`gen:spec-changes`, drift-checked in CI), |
| 338 | + ships inside the npm artifact together with `api-surface.json`, and is |
| 339 | + attached to each `@objectstack/spec` GitHub Release with the `added[]`/ |
| 340 | + `removed[]` arrays filled from the api-surface diff against the previously |
| 341 | + *published* release (`scripts/release-spec-changes.sh`). The upgrade guide is |
| 342 | + now literally a projection: `docs/protocol-upgrade-guide.md` is generated |
| 343 | + from the registries (`gen:upgrade-guide`) and drift-checked in CI. |
| 344 | +- **P3 — MCP tools + RC discipline (D6): deferred, evidence-gated** (as the ADR |
| 345 | + Boundaries intended): built when external-consumer demand justifies the |
| 346 | + operational surface. Nothing else in this ADR depends on it. |
| 347 | + |
| 348 | +### The load-window's second half is now mechanical |
| 349 | + |
| 350 | +`MetadataConversion.retiredFromLoadPath` implements "retired from the load path |
| 351 | +in N+1 — but never deleted": a retired entry is skipped by the loader |
| 352 | +(`applyConversions`) and replayed only by the chain (`migrate meta`) and the |
| 353 | +fixture CI. Live-window entries (currently the protocol-15 ADR-0089 visibility |
| 354 | +aliases) stay load-active until they graduate. |
| 355 | + |
| 356 | +### Ratified: the pre-launch launch-window exemption (majors 12–15) |
| 357 | + |
| 358 | +Majors 12–14 shipped breaks as **pre-launch one-step changes with no alias |
| 359 | +window** (ADR-0090 D3/D4 explicitly superseded the alias discipline; |
| 360 | +`BookAudience` in 14.0.0 states "launch-window discipline"). That was a |
| 361 | +deliberate policy while the platform had no external consumers — but it was |
| 362 | +never written down, and it left the chain empty above step 11. This true-up |
| 363 | +does both halves: |
| 364 | + |
| 365 | +- **The policy, stated:** until GA, a metadata-facing break MAY ship one-step |
| 366 | + without a load window. The exemption covers the *window* only — never the |
| 367 | + *chain*: every such break must land as a chain step (a `retiredFromLoadPath` |
| 368 | + conversion when lossless, a semantic TODO when not) in the same release. |
| 369 | + After GA the full D2 ladder applies: lossless breaks ship a live conversion |
| 370 | + entry or they do not ship. |
| 371 | +- **The chain, backfilled:** steps 12–15 now exist. 12: the `api.requireAuth` |
| 372 | + default flip (semantic). 13: the ADR-0090 wave — `roles:`→`positions:`, the |
| 373 | + two unambiguous OWD aliases, and recipient `role`→`position` as retired |
| 374 | + conversions; profiles, hierarchy re-homing, `current_user.roles` CEL |
| 375 | + rewrites, the `'full'` alias, and the sharing-model secure default as |
| 376 | + semantic TODOs. 14: the `BookAudience` rename (retired conversion). 15: the |
| 377 | + ADR-0089 visibility aliases (live conversions) plus the `.strict()` flip |
| 378 | + (semantic). `migrate meta --from 10` therefore reaches protocol 15 with |
| 379 | + every mechanical rewrite applied and every judgment surfaced — the "arrive |
| 380 | + whenever" promise holds across the pre-launch era too. |
| 381 | +- **Backfilled history joins the registry, not the loader:** the protocol-11 |
| 382 | + `compactLayout`→`highlightFields` rename (retired at authoring in 11.9.1, |
| 383 | + pre-dating this ADR) is also preserved as a retired step-11 conversion. |
0 commit comments