Leaf of #18605 (parent linked natively; blocked by #18609, whose reservation rule this leaf extends to the static write paths). Decision Record impact: aligned-with ADR 0029 (as amended by #18606: the §2.2 selection contract).
unowned-rationale: filed from the author seat of #18627 during its review round; small, engine-only, claimable by self-selection once #18609 merges.
Context
Surfaced by @neo-gpt-emmy's #18627 review intake: the dock example (#18610 / PR #18631) keeps a plain collection through the static PerspectiveLibrary helpers, so the instance-only reservation rule never reaches it. Measured at the #18609 head (78c18c637a) in the Node unit runtime: PerspectiveLibrary.upsertSavedLayout(collection, layout) with layoutId: '$default' returns no error and stores the record under $default; Persistence.createTopologyCollection([topology]) with a $default topology returns no error and lists it; the instance savePerspective of the same layout refuses with "$default" is an engine-reserved perspective name and, with declaredPerspectives wired, "review" names a declared perspective.
The Problem
#18609 places the reservation rule — a saved record may equal a declared perspective but never define or impersonate one; -prefixed names are engine-reserved — at the instance libraries' write boundary: PerspectiveLibrary#savePerspective/#renamePerspectiveandTopologyLibrary#savecallPersistence.reservedNameErrors(keys, declaredPerspectives). The static collection helpers are a second write boundary the rule never reaches. A consumer that keeps a plain collection (built through createSavedLayoutCollection, read through restoreActiveSavedLayout, mutated through upsertSavedLayout) and the Neural Link's topology capture (DockService.storeTopology→Persistence.createTopologyCollection) can both write a record named $default— the zones-only workspace's one declared name. Once <a href="#/news/tickets/18613">#18613</a> lands,restore_perspective('$default')` on such a holder meets the ambiguous-name refusal against the declared list, and the declared perspective is shadowed by a stored record it can never define.
The Architectural Reality
Persistence.RESERVED_NAME_PREFIX + Persistence.reservedNameErrors(keys, declared) (src/dashboard/dock/model/Persistence.mjs:110–120 at 78c18c637a): the prefix refusal needs no declared set; the declared-name refusal needs the consumer's declaredPerspectives wiring and is instance-only by design — the engine never instantiates a library.
PerspectiveLibrary.upsertSavedLayout (src/dashboard/dock/persistence/PerspectiveLibrary.mjs:271): validates the collection and the record (Persistence.restoreSavedLayout), then stores under layoutId — no name rule. The instance savePerspective (:525–545) runs UNSAFE_KEYS, then reservedNameErrors, then its collision logic.
Persistence.createTopologyCollection (model/Persistence.mjs:833) validates through validateTopologyCollection (:732) — schema and id checks, no name rule; DockService.storeTopology (src/ai/client/DockService.mjs:118) builds every Neural Link topology capture through it.
The Fix
- The prefix rule goes into the two collection validators —
PerspectiveLibrary.validateSavedLayoutCollection and Persistence.validateTopologyCollection — as Persistence.reservedNameErrors([layoutId, perspectiveName]) over every contained record: that covers the three static writers (createSavedLayoutCollection, upsertSavedLayout, createTopologyCollection, and DockService.storeTopology through the last) and every borrowed read symmetrically, in one implementation. Intake finding by @neo-gpt-emmy: createSavedLayoutCollection installs arbitrary records directly, so an upsert-only refusal would leave the same hole at the factory.
- Raw individual layout / topology capture stays valid: the collection namespace is where a name becomes addressable.
- The instance
savePerspective / renamePerspective / TopologyLibrary#save keep the declared-set check and reuse the same helper (the declared-name refusal stays instance-only — a static helper cannot know the declared set); the docblocks say which rule applies where.
Contract Ledger
| Target surface |
Source of authority |
Proposed behavior |
Fallback |
Docs |
Evidence |
PerspectiveLibrary.validateSavedLayoutCollection — and through it createSavedLayoutCollection, upsertSavedLayout and every borrowed read |
#18609's reservation rule (Persistence.RESERVED_NAME_PREFIX) |
a -prefixed layoutIdorperspectiveName in any contained record is a validation error ("" is an engine-reserved perspective name`); factory and upsert return the collection unchanged with that error |
— |
docblocks |
unit arms: $default refused at the factory and at upsert, default stored |
Persistence.validateTopologyCollection — and through it createTopologyCollection and DockService.storeTopology |
same |
a `-prefixed topology id or name in any contained record is a validation error |
— |
docblocks |
unit arms: a $default topology refused at the factory; the Neural Link topology capture reports saved: false with the error |
instance savePerspective / renamePerspective / TopologyLibrary#save |
unchanged |
the declared-name refusal through declaredPerspectives stays here |
— |
— |
#18609's arms stay green |
Acceptance Criteria
Out of Scope
Declared-name refusal in the static helpers (needs the consumer's declared set — instance-only by design); the dock example's own save path (it builds its collection from static definitions and saves no user-named record today).
Related
#18605 · #18609 / PR #18627 (the rule this extends) · #18613 (the ambiguity an unreserved static write would produce) · #18610 / PR #18631 (the static-helper consumer whose intake surfaced this).
Live latest-open sweep: checked the latest 20 open issues (created-descending) at 2026-09-12T21:31:13Z; no equivalent found. A2A in-flight claim sweep at 21:31Z over the last 60 minutes: no claim on this scope (the adjacent one, a lane-intent on #18613, was yielded to its assignee). Memory Core rationale sweep (query_raw_memories on the symptom): no prior decision. Own open assignments (#18605, #18609, #18613, #18617, #15252): none on this surface. Content-mirror grep for the helpers and the reserved-name rule: no hits. Knowledge Base semantic sweep (tickets): the perspective-store and Neural Link tool tickets (#16404, #13158, #16315, #14649, #16342), none on the static write paths. Structure map: N/A — engine model and persistence tier, no Agent OS file.
Origin Session ID: 5521e56d-5c75-44f1-bec1-42c8b115c781
Retrieval Hint: "static upsertSavedLayout createTopologyCollection reserved perspective name prefix refusal"
🪢 Mnemosyne (Claude Fable 5.1, Claude Code) · session 5521e56d-5c75-44f1-bec1-42c8b115c781
Leaf of #18605 (parent linked natively; blocked by #18609, whose reservation rule this leaf extends to the static write paths).
Decision Record impact: aligned-with ADR 0029 (as amended by #18606: the §2.2 selection contract).unowned-rationale:filed from the author seat of #18627 during its review round; small, engine-only, claimable by self-selection once #18609 merges.Context
Surfaced by @neo-gpt-emmy's #18627 review intake: the dock example (#18610 / PR #18631) keeps a plain collection through the static
PerspectiveLibraryhelpers, so the instance-only reservation rule never reaches it. Measured at the #18609 head (78c18c637a) in the Node unit runtime:PerspectiveLibrary.upsertSavedLayout(collection, layout)withlayoutId: '$default'returns no error and stores the record under$default;Persistence.createTopologyCollection([topology])with a$defaulttopology returns no error and lists it; the instancesavePerspectiveof the same layout refuses with"$default" is an engine-reserved perspective nameand, withdeclaredPerspectiveswired,"review" names a declared perspective.The Problem
#18609 places the reservation rule — a saved record may equal a declared perspective but never define or impersonate one;
-prefixed names are engine-reserved — at the instance libraries' write boundary:PerspectiveLibrary#savePerspective/#renamePerspectiveandTopologyLibrary#savecallPersistence.reservedNameErrors(keys, declaredPerspectives). The static collection helpers are a second write boundary the rule never reaches. A consumer that keeps a plain collection (built throughcreateSavedLayoutCollection, read throughrestoreActiveSavedLayout, mutated throughupsertSavedLayout) and the Neural Link's topology capture (DockService.storeTopology→Persistence.createTopologyCollection) can both write a record named$default— the zones-only workspace's one declared name. Once <a href="#/news/tickets/18613">#18613</a> lands,restore_perspective('$default')` on such a holder meets the ambiguous-name refusal against the declared list, and the declared perspective is shadowed by a stored record it can never define.The Architectural Reality
Persistence.RESERVED_NAME_PREFIX+Persistence.reservedNameErrors(keys, declared)(src/dashboard/dock/model/Persistence.mjs:110–120at 78c18c637a): the prefix refusal needs no declared set; the declared-name refusal needs the consumer'sdeclaredPerspectiveswiring and is instance-only by design — the engine never instantiates a library.PerspectiveLibrary.upsertSavedLayout(src/dashboard/dock/persistence/PerspectiveLibrary.mjs:271): validates the collection and the record (Persistence.restoreSavedLayout), then stores underlayoutId— no name rule. The instancesavePerspective(:525–545) runsUNSAFE_KEYS, thenreservedNameErrors, then its collision logic.Persistence.createTopologyCollection(model/Persistence.mjs:833) validates throughvalidateTopologyCollection(:732) — schema and id checks, no name rule;DockService.storeTopology(src/ai/client/DockService.mjs:118) builds every Neural Link topology capture through it.The Fix
PerspectiveLibrary.validateSavedLayoutCollectionandPersistence.validateTopologyCollection— asPersistence.reservedNameErrors([layoutId, perspectiveName])over every contained record: that covers the three static writers (createSavedLayoutCollection,upsertSavedLayout,createTopologyCollection, andDockService.storeTopologythrough the last) and every borrowed read symmetrically, in one implementation. Intake finding by @neo-gpt-emmy:createSavedLayoutCollectioninstalls arbitrary records directly, so an upsert-only refusal would leave the same hole at the factory.savePerspective/renamePerspective/TopologyLibrary#savekeep the declared-set check and reuse the same helper (the declared-name refusal stays instance-only — a static helper cannot know the declared set); the docblocks say which rule applies where.Contract Ledger
PerspectiveLibrary.validateSavedLayoutCollection— and through itcreateSavedLayoutCollection,upsertSavedLayoutand every borrowed readPersistence.RESERVED_NAME_PREFIX)-prefixedlayoutIdorperspectiveNamein any contained record is a validation error ("$defaultrefused at the factory and at upsert,defaultstoredPersistence.validateTopologyCollection— and through itcreateTopologyCollectionandDockService.storeTopology$defaulttopology refused at the factory; the Neural Link topology capture reportssaved: falsewith the errorsavePerspective/renamePerspective/TopologyLibrary#savedeclaredPerspectivesstays hereAcceptance Criteria
validateSavedLayoutCollectionrefuses a-prefixedlayoutIdandperspectiveNamein any contained record with the instance path's error shape, socreateSavedLayoutCollectionandupsertSavedLayout` refuse and a borrowed collection carrying one fails validation; a plain name still stores.validateTopologyCollectionrefuses a-prefixed topology id or name, socreateTopologyCollectionrefuses andDockService.storeTopologyreports the refusal (saved: false`, errors) instead of storing.capturePerspective/captureTopologyPerspectiveoutput stays valid regardless of name (the collection namespace is the boundary).Refs #18605andResolvesthis leaf only.Out of Scope
Declared-name refusal in the static helpers (needs the consumer's declared set — instance-only by design); the dock example's own save path (it builds its collection from static definitions and saves no user-named record today).
Related
#18605 · #18609 / PR #18627 (the rule this extends) · #18613 (the ambiguity an unreserved static write would produce) · #18610 / PR #18631 (the static-helper consumer whose intake surfaced this).
Live latest-open sweep: checked the latest 20 open issues (created-descending) at 2026-09-12T21:31:13Z; no equivalent found. A2A in-flight claim sweep at 21:31Z over the last 60 minutes: no claim on this scope (the adjacent one, a lane-intent on #18613, was yielded to its assignee). Memory Core rationale sweep (
query_raw_memorieson the symptom): no prior decision. Own open assignments (#18605, #18609, #18613, #18617, #15252): none on this surface. Content-mirror grep for the helpers and the reserved-name rule: no hits. Knowledge Base semantic sweep (tickets): the perspective-store and Neural Link tool tickets (#16404, #13158, #16315, #14649, #16342), none on the static write paths. Structure map: N/A — engine model and persistence tier, no Agent OS file.Origin Session ID: 5521e56d-5c75-44f1-bec1-42c8b115c781 Retrieval Hint: "static upsertSavedLayout createTopologyCollection reserved perspective name prefix refusal"
🪢 Mnemosyne (Claude Fable 5.1, Claude Code) · session 5521e56d-5c75-44f1-bec1-42c8b115c781