Serves: #18303 — multi-window dock transactions, topology, and reload. The persisted envelope's version discipline is that epic's surface.
The Problem
neo.dock.zone.v1's field set changed incompatibly and its version tag did not move, so a document declares a schema it can no longer satisfy.
#18536 retires componentRef from WorkspaceDocument.dockZoneItemKeys and adds reference. The allowlist is strict and findUnexpectedKey rejects rather than drops, so a document written by any earlier build now fails validation — while still calling itself v1, the same tag today's build accepts.
Traced end to end at 51b37aa8a5:
| step |
behaviour |
Persistence.mjs:539 |
per workspace: WorkspaceDocument.validate(document) + findUnexpectedDockZoneKey(...) |
WorkspaceDocument.mjs:276 |
findUnexpectedKey returns {key, path, reason} — a rejection, not a drop |
TopologyLibrary.mjs:405 |
adoptCollection → {hydrated: false, errors} |
Viewport.mjs:120 |
if (errors.length) { library.destroy(); showRootFailure(); return } |
A user upgrading past that commit with a saved Workstation layout gets the failure panel. They are not stranded — showRootFailure "offers an explicit fresh root", exactly as documented — but the layout is discarded, and the discard arrives as a validation error rather than as a recognised version change.
The Architectural Reality
This is not a request for backward compatibility, and the policy that forbids it is already ratified. #18308 (CLOSED) hard-cut dock persistence with "Zero backwards compatibility is explicit", put "a migration reader" in its Out of Scope, and recorded the fallback for the zone document as "none; old class/import fails". That decision stands and nothing here reopens it.
What #18308 also ratified is the trap this falls into. Its Avoided Traps list reads:
No silent redefinition of layout.v1
v1 was held stable there because the rename did not change what a zone document contains. #18536 does change it. The same trap, one envelope over.
The mechanism exists and this repo has already used it. Persistence.mjs:505 hard-requires topologyFingerprint.schema === 'neo.dock.topologyShape.v2' — a dock schema tag that was bumped when its shape changed, and is enforced on read. neo.dock.zone.v1 is the sibling that did not get the same treatment.
Three version-ish mechanisms are in play and none of them is a schema version:
| mechanism |
what it actually is |
WorkspaceDocument.SCHEMA = 'neo.dock.zone.v1' |
the real schema tag — unchanged, and not enforced on read the way topologyShape.v2 is |
TopologyLibrary.#version (:90, starts at 0) |
a session-local optimistic-concurrency counter that fences concurrent writes; it is not a persisted schema version |
indexedDB.open('neo-dock-topologies', 1) (:34) |
the store version, with the onupgradeneeded seam already wired |
⚠️ Do not mistake the second for the first. #version reads like a version and answers a different question; an implementer who bumps it changes nothing about stored-document compatibility.
The Fix
Bump the zone envelope past v1 and enforce it on read, mirroring topologyShape.v2. A document carrying the retired tag is then recognised as old and reset deliberately — with whatever surface #18303 wants — instead of failing an allowlist check and surfacing as showRootFailure. No migration reader; the hard cut stays a hard cut, it simply stops being silent.
Contract Ledger
| Target surface |
Source of authority |
Behavior |
Fallback |
Docs |
Evidence |
WorkspaceDocument.SCHEMA |
#18308's zone-document row, now superseded on the field-set axis |
tag moves past neo.dock.zone.v1 when the field set changes |
a document carrying the retired tag is recognised as old, not as malformed |
ADR 0029 + DockZoneModel.md |
a stored fixture at the retired tag reaching the recognised-old path, not the error path |
| zone-document read validation |
topologyShape.v2's enforced-tag precedent |
the tag is checked on read, as Persistence.mjs:505 already checks the fingerprint's |
unknown/absent tag refuses with a named reason |
same |
positive control at the current tag, negative at the retired one |
| the reset surface |
#18303 |
out of scope here — this ticket only makes the version legible; #18303 owns what the user sees |
showRootFailure remains the behaviour until #18303 changes it |
— |
— |
AC
- AC-1
WorkspaceDocument.SCHEMA moves past neo.dock.zone.v1, and the tag is enforced on read the way topologyFingerprint.schema already is at Persistence.mjs:505.
- AC-2 A stored document at the retired tag is distinguishable from a malformed one: it reaches a recognised-old path with a named reason, not
findUnexpectedKey's generic refusal.
- AC-3 ⚠️ Both controls, or the change is unobserved. A fixture at the current tag hydrates; a fixture at the retired tag takes the recognised-old path. A test that only asserts the second cannot tell a working bump from a broken validator.
- AC-4 No migration reader and no field-level tolerance — #18308's hard-cut policy is unchanged. This makes the cut legible, it does not soften it.
- AC-5 The coupling is recorded where an implementer will hit it: editing
dockZoneItemKeys obliges a tag decision. State whether that is enforceable mechanically; if it is, say what would enforce it, and if it is not, say why.
Out of Scope
Any migration or compatibility reader (#18308, explicit). Retaining componentRef — #18528 settled that and @tobiu ruled backward compatibility is not an argument. What the user is shown on a recognised-old document: that is #18303's, and this ticket deliberately stops at making the version legible. TopologyLibrary.#version semantics. The IndexedDB store version, unless the implementer finds onupgradeneeded is the cheaper seam — then it is a means, not a new scope.
Avoided Traps
Do not read this as backward compatibility — it is the opposite: an explicit break, made explicit. Do not bump TopologyLibrary.#version and believe the job is done; it is a session-local concurrency counter, not a schema version, and it is the most likely wrong turn here. Do not add a compatibility branch that accepts both field sets — that is exactly the tolerance #18528 removed. Do not treat showRootFailure as a bug: it is the documented recovery, and the defect is that a version change reaches it disguised as corruption.
Decision Record impact
amends ADR 0029's persisted-envelope section — it names neo.dock.zone.v1, which this moves. #18308's hard-cut policy is unchanged; only its "zone.v1 unchanged" row is superseded, and only because the field set moved under it.
Sweeps and ownership
Live latest-open sweep: latest 20 open issues read at 2026-09-09T11:0xZ — nothing equivalent. All-state searches for schema/version/migration and for neo.dock.zone.v1 surfaced #18308 (CLOSED, the governing policy, read in full), #18303 (parent), #18475 and #18528 — no duplicate. A2A sweep over the latest 30 messages, all read-states: no [lane-claim] on persistence versioning.
Found by @neo-opus-grace while reviewing #18536 against #18528, which is my own ticket — the break is downstream of an AC I wrote, and #18536's author reported the IndexedDB reach herself rather than using it as grounds to keep her design.
Retrieval Hint: neo.dock.zone.v1 schema bump topologyShape.v2 dockZoneItemKeys showRootFailure silent redefinition
Origin Session ID: c4dc8abc-31fa-4ecd-9aef-5209ccfd16c0
Serves:#18303 — multi-window dock transactions, topology, and reload. The persisted envelope's version discipline is that epic's surface.The Problem
neo.dock.zone.v1's field set changed incompatibly and its version tag did not move, so a document declares a schema it can no longer satisfy.#18536 retires
componentReffromWorkspaceDocument.dockZoneItemKeysand addsreference. The allowlist is strict andfindUnexpectedKeyrejects rather than drops, so a document written by any earlier build now fails validation — while still calling itselfv1, the same tag today's build accepts.Traced end to end at
51b37aa8a5:Persistence.mjs:539WorkspaceDocument.validate(document)+findUnexpectedDockZoneKey(...)WorkspaceDocument.mjs:276findUnexpectedKeyreturns{key, path, reason}— a rejection, not a dropTopologyLibrary.mjs:405adoptCollection→{hydrated: false, errors}Viewport.mjs:120if (errors.length) { library.destroy(); showRootFailure(); return }A user upgrading past that commit with a saved Workstation layout gets the failure panel. They are not stranded —
showRootFailure"offers an explicit fresh root", exactly as documented — but the layout is discarded, and the discard arrives as a validation error rather than as a recognised version change.The Architectural Reality
This is not a request for backward compatibility, and the policy that forbids it is already ratified. #18308 (CLOSED) hard-cut dock persistence with "Zero backwards compatibility is explicit", put "a migration reader" in its Out of Scope, and recorded the fallback for the zone document as "none; old class/import fails". That decision stands and nothing here reopens it.
What #18308 also ratified is the trap this falls into. Its Avoided Traps list reads:
v1was held stable there because the rename did not change what a zone document contains. #18536 does change it. The same trap, one envelope over.The mechanism exists and this repo has already used it.
Persistence.mjs:505hard-requirestopologyFingerprint.schema === 'neo.dock.topologyShape.v2'— a dock schema tag that was bumped when its shape changed, and is enforced on read.neo.dock.zone.v1is the sibling that did not get the same treatment.Three version-ish mechanisms are in play and none of them is a schema version:
WorkspaceDocument.SCHEMA = 'neo.dock.zone.v1'topologyShape.v2isTopologyLibrary.#version(:90, starts at0)indexedDB.open('neo-dock-topologies', 1)(:34)onupgradeneededseam already wired⚠️ Do not mistake the second for the first.
#versionreads like a version and answers a different question; an implementer who bumps it changes nothing about stored-document compatibility.The Fix
Bump the zone envelope past
v1and enforce it on read, mirroringtopologyShape.v2. A document carrying the retired tag is then recognised as old and reset deliberately — with whatever surface #18303 wants — instead of failing an allowlist check and surfacing asshowRootFailure. No migration reader; the hard cut stays a hard cut, it simply stops being silent.Contract Ledger
WorkspaceDocument.SCHEMAneo.dock.zone.v1when the field set changesDockZoneModel.mdtopologyShape.v2's enforced-tag precedentPersistence.mjs:505already checks the fingerprint'sshowRootFailureremains the behaviour until #18303 changes itAC
WorkspaceDocument.SCHEMAmoves pastneo.dock.zone.v1, and the tag is enforced on read the waytopologyFingerprint.schemaalready is atPersistence.mjs:505.findUnexpectedKey's generic refusal.dockZoneItemKeysobliges a tag decision. State whether that is enforceable mechanically; if it is, say what would enforce it, and if it is not, say why.Out of Scope
Any migration or compatibility reader (#18308, explicit). Retaining
componentRef— #18528 settled that and @tobiu ruled backward compatibility is not an argument. What the user is shown on a recognised-old document: that is #18303's, and this ticket deliberately stops at making the version legible.TopologyLibrary.#versionsemantics. The IndexedDB store version, unless the implementer findsonupgradeneededis the cheaper seam — then it is a means, not a new scope.Avoided Traps
Do not read this as backward compatibility — it is the opposite: an explicit break, made explicit. Do not bump
TopologyLibrary.#versionand believe the job is done; it is a session-local concurrency counter, not a schema version, and it is the most likely wrong turn here. Do not add a compatibility branch that accepts both field sets — that is exactly the tolerance #18528 removed. Do not treatshowRootFailureas a bug: it is the documented recovery, and the defect is that a version change reaches it disguised as corruption.Decision Record impact
amendsADR 0029's persisted-envelope section — it namesneo.dock.zone.v1, which this moves. #18308's hard-cut policy is unchanged; only its "zone.v1 unchanged" row is superseded, and only because the field set moved under it.Sweeps and ownership
Live latest-open sweep: latest 20 open issues read at 2026-09-09T11:0xZ — nothing equivalent. All-state searches for schema/version/migration and for
neo.dock.zone.v1surfaced #18308 (CLOSED, the governing policy, read in full), #18303 (parent), #18475 and #18528 — no duplicate. A2A sweep over the latest 30 messages, all read-states: no[lane-claim]on persistence versioning.Found by @neo-opus-grace while reviewing #18536 against #18528, which is my own ticket — the break is downstream of an AC I wrote, and #18536's author reported the IndexedDB reach herself rather than using it as grounds to keep her design.
Retrieval Hint:
neo.dock.zone.v1 schema bump topologyShape.v2 dockZoneItemKeys showRootFailure silent redefinitionOrigin Session ID: c4dc8abc-31fa-4ecd-9aef-5209ccfd16c0