Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,9 @@ See `ui/AGENTS.md` for the app-id / localStorage / auth gotchas.
"be liberal in what you accept" does **not** apply. Change the **spec** only when the spec itself is genuinely
wrong, and then deliberately (edit the Zod schema + migrate). When an alias must be tolerated at all, declare it
as an **ADR-0087 conversion-layer entry** (never a bare `??`, no executor shims) so it is declared, loud, tested,
and *removable on a schedule*. Stored `sys_metadata` rows are covered from the other side: every rehydration seam
replays the **full** conversion chain — retired entries included — via `applyConversionsToStoredItem` (ADR-0087
addendum), so a consumer never needs its own accommodation for a legacy stored shape either. Strengthens #5.
and *removable on a schedule*. Stored rows: `applyConversionsToStoredItem` replays the chain the installed release
carries, retired entries included; kits age out one major after retiring (ADR-0087 as amended, ruling 6434038284),
so run `os migrate meta --stored --apply` before leaving a release that still converts the row. Strengthens #5.
13. **An accepted ADR binds until a superseding ADR says otherwise.** Reversing a recorded decision is itself a
decision: it needs a **new ADR** (or an amended status line on the old one), not a changeset that quietly does the
opposite. Before changing behaviour in `docs/adr/`-governed territory, **grep the ADRs for the surface you are
Expand Down
23 changes: 16 additions & 7 deletions packages/metadata-protocol/src/stored-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
* (#4327), the follow-up to #3903's read-path guarantee.
*
* #4317 closed the correctness gap from the read side: every stored-row
* rehydration seam replays the full ADR-0087 conversion chain
* (`applyConversionsToStoredItem`, retired entries included), so a row written
* under any past protocol is *served* canonical forever. What it deliberately
* rehydration seam replays the ADR-0087 conversion chain the installed release
* carries (`applyConversionsToStoredItem`, retired entries included), so a row
* is *served* canonical while that release still carries its conversions. A
* retirement kit ages out one major after it retires (ADR-0087 as amended,
* ruling 6434038284), so this pass must run on a release that still carries a
* row's conversions, before the deployment moves to the release that drops
* them. Past that, the seams still read and serve the row's legacy bytes, but
* no conversion recognizes its shape, so this pass counts it `canonical`: a
* false clean, not evidence. What it deliberately
* did not do is make the rows themselves canonical — a pre-17 row keeps its
* legacy bytes, the chain re-lowers it on every load, and each affected row
* emits one conversion notice per process.
Expand All @@ -19,10 +25,13 @@
* the types rather than in the CLI so an admin route renders the same run the
* same way.
*
* **Not load-bearing.** #3855's conclusion still holds — an operator-run
* migration cannot be relied on, so the read path stays the guarantee and
* nothing gates on this having run. No `sys_migration` flag is recorded for
* exactly that reason: a flag row would advertise a gate that does not exist.
* **Not load-bearing while the release carries a row's conversions.** #3855's
* conclusion holds within that window — an operator-run migration cannot be
* relied on, so the read path stays the guarantee there and nothing gates on
* this having run; for a row whose conversions the next release drops, this
* pass is the required step above, not an optional one. No `sys_migration`
* flag is recorded for exactly that reason: a flag row would advertise a gate
* that does not exist.
* The verifiable statement operators wanted is the *re-run* — a second pass
* that reports every row canonical is the evidence, and it costs one command.
*/
Expand Down
4 changes: 3 additions & 1 deletion packages/spec/src/conversions/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ export interface ApplyConversionsOptions {
* have no author to teach, so each replays the FULL chain, retired entries
* included — ADR-0087's `## Addendum (2026-07-31)` for the first two, the
* #12772 ruling for the third. The fixture CI sets it as well, so graduated
* transforms stay covered forever (ADR-0087 D3).
* transforms stay covered while the installed release carries them; a
* retirement kit ages out one major after it retires (ADR-0087 as amended,
* ruling 6434038284), and its transform leaves coverage with it.
*
* `objectstack migrate meta` is NOT one of these callers: `applyMetaMigrations`
* (`../migrations/chain.js`) looks each step's conversion up in
Expand Down
13 changes: 9 additions & 4 deletions packages/spec/src/conversions/stored.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
* {@link applyConversionsToStoredItem} is the one primitive every stored-row
* rehydration seam calls, and it encodes the policy for data at rest:
*
* - **The full chain replays, including `retiredFromLoadPath` entries.**
* - **The chain the installed release carries replays, including
* `retiredFromLoadPath` entries.**
* Retirement is an *authoring-surface* event — the schema tombstones the
* key so a live author is taught the canonical spelling. A stored row has
* no author; refusing its historical shape would not teach anyone, it
* would only break data that once worked. D3 keeps every conversion
* forever precisely so any past major can replay forward — a row at rest
* is the perpetual "consumer arriving late".
* would only break data that once worked. But a retirement kit ages out
* one major after it retires (ADR-0087 as amended, ruling 6434038284), and
* its conversion leaves with it: a row still in that shape is no longer
* recognized here, so it must be canonicalized by the stored pass
* (`os migrate meta --stored --apply`) on a release that still carries
* the row's conversions, before the deployment moves to the release that
* drops them.
* - **Idempotent by construction.** Conversions only rewrite shapes they
* positively recognize, so re-converting an already-canonical item is a
* no-op — seams may safely stack (e.g. a row converted here and again at
Expand Down
Loading