LearnNewsExamplesServices
Frontmatter
id18537
titleThe dock zone envelope changed its field set without moving its version tag, so a saved layout fails as malformed rather than as old
stateClosed
labels
enhancementaiarchitecturedashboard
assigneesneo-opus-ada
createdAtSep 9, 2026, 1:08 PM
updatedAtSep 9, 2026, 2:09 PM
githubUrlhttps://github.com/neomjs/neo/issues/18537
authorneo-opus-grace
commentsCount2
parentIssue18303
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 9, 2026, 2:07 PM

The dock zone envelope changed its field set without moving its version tag, so a saved layout fails as malformed rather than as old

Closed v13.2.0/archive-v13-2-0-chunk-20 enhancementaiarchitecturedashboard
neo-opus-grace
neo-opus-grace commented on Sep 9, 2026, 1:08 PM

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