Skip to content

Commit 8fc1208

Browse files
os-zhuangclaude
andauthored
feat(protocol): complete ADR-0087 — load-seam handshake, chain backfill 12–15, release artifacts (#2972)
Closes the remaining gaps of the ADR-0087 epic (#2643); trues the ADR up to as-built and ratifies the pre-launch launch-window exemption. - P0 load seams (D1): handshake on sys_packages boot rehydration (structured refusal, boot continues) and on AppPlugin for code-defined stacks; new objectstack lint rule protocol/missing-engines-range; create-objectstack template stamps engines.protocol (version-time re-stamp + ratchet test). - Chain backfill (D2/D3): MetadataConversion.retiredFromLoadPath implements the load-window's second half; new conversions for majors 11/13/14/15 (incl. ADR-0089 visibleOn/visibility → visibleWhen as live windows) and chain steps 12–15 with semantic TODOs. migrate meta --from 10 reaches 15, verified end-to-end. - Release artifacts (D4): spec-changes.json generated + CI drift-checked + shipped in the npm artifact + attached to GitHub Releases with the api-surface diff; docs/protocol-upgrade-guide.md generated from the registries with a CI drift check. Refs #2643, #2644, #2648 · Closes #2645 · Closes #2647 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LMCyH8zFQQsnR9r46fhz4c
1 parent 3dc9fce commit 8fc1208

30 files changed

Lines changed: 1855 additions & 23 deletions

.changeset/adr-0087-completion.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/cli': minor
4+
'@objectstack/runtime': minor
5+
'@objectstack/service-package': minor
6+
'create-objectstack': minor
7+
---
8+
9+
feat(protocol): complete ADR-0087 — load-seam handshake, chain backfill 12–15, release artifacts (#2643)
10+
11+
Closes the remaining ADR-0087 gaps (see the ADR's as-built Addendum):
12+
13+
- **P0 load seams (D1).** The protocol handshake now runs on the boot-time
14+
durable-package rehydration path (`@objectstack/service-package` refuses an
15+
incompatible `sys_packages` row with the structured `OS_PROTOCOL_INCOMPATIBLE`
16+
diagnostic and keeps booting) and on `AppPlugin` for code-defined stacks
17+
(fail-fast before the manifest is decomposed). `objectstack lint` gains
18+
`protocol/missing-engines-range` (warning + fix-it) and the
19+
`create-objectstack` blank template stamps `engines: { protocol: '^<major>' }`
20+
(re-stamped at version time by `scripts/sync-template-versions.mjs`) — the
21+
two ends of the grandfathering ratchet.
22+
- **Chain backfill (D2/D3).** `MetadataConversion.retiredFromLoadPath`
23+
implements the load-window's second half (retired entries replay only via
24+
`migrate meta` / fixture CI). Steps 12–15 land: the `api.requireAuth` flip
25+
(semantic), the ADR-0090 wave (3 retired conversions + 5 semantic TODOs), the
26+
`BookAudience` rename (retired conversion), and the ADR-0089 visibility
27+
unification (`visibleOn`/`visibility``visibleWhen` as LIVE load-window
28+
conversions) + the `.strict()` flip (semantic). The protocol-11
29+
`compactLayout``highlightFields` rename is backfilled as a retired step-11
30+
conversion. `migrate meta --from 10` now reaches protocol 15.
31+
- **Release artifacts (D4).** `spec-changes.json` is generated from the
32+
registries (`gen:spec-changes`, CI drift-checked), ships in the npm artifact
33+
together with `api-surface.json`, and is attached to each `@objectstack/spec`
34+
GitHub Release with `added[]`/`removed[]` filled from the api-surface diff
35+
against the previously published release. The upgrade guide
36+
(`docs/protocol-upgrade-guide.md`) is generated from the same registries and
37+
CI drift-checked — a projection that cannot drift.

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,9 @@ jobs:
389389

390390
- name: Check skill docs are generated from SKILL.md frontmatter
391391
run: pnpm --filter @objectstack/spec check:skill-docs
392+
393+
- name: Check spec-changes.json is regenerated with the ADR-0087 registries
394+
run: pnpm --filter @objectstack/spec check:spec-changes
395+
396+
- name: Check the protocol upgrade guide is regenerated with the ADR-0087 registries
397+
run: pnpm --filter @objectstack/spec check:upgrade-guide

.github/workflows/release.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,17 @@ jobs:
9898
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
9999
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
100100

101+
- name: Attach spec-changes.json to the GitHub Release (ADR-0087 D4)
102+
# Rebuilds the change manifest with the api-surface diff against the
103+
# previously PUBLISHED spec (reusing the ADR-0059 §3 gate artifact) and
104+
# uploads it to the @objectstack/spec release the changesets action
105+
# just created. The npm artifact carries the registry-derived copy.
106+
if: steps.changesets.outputs.published == 'true'
107+
env:
108+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
109+
PUBLISHED: ${{ steps.changesets.outputs.publishedPackages }}
110+
run: bash scripts/release-spec-changes.sh
111+
101112
- name: Extract published @objectstack/cli version
102113
id: cli-version
103114
if: steps.changesets.outputs.published == 'true'

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

Lines changed: 70 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**: Proposed (2026-07-04)
3+
**Status**: Accepted (2026-07-04, #2582) · trued up to as-built 2026-07-15 (see Addendum)
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**
@@ -312,3 +312,72 @@ scaling framework-side execution when the consumers can execute themselves.
312312
consumer still gets strictly more than the pre-ADR world (fail-fast, a generated
313313
guide, runnable migrations) — the bet has no downside for them, only a smaller
314314
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

Comments
 (0)