Files
XZBT-NGN/docs/architecture/XZBT-Multi-Surface-Model-Step6.1.md
T

756 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# XZBT Multi-Surface Presentation Model — Step 6.1
## Status
**Revision 2 — for final approval.** Contract 5.3 has been approved by the
owner as the intended version for multi-surface presentation (Section 14).
The versioned `v5.3.md` contract file has **not** been minted and Phase 6.2
has **not** begun; both remain gated on approval of this revised document.
This revision addresses five items raised against Revision 1:
1. defines a generic surface-attachment mechanism (new Section 7.2);
2. restores the canonical target-ID grammar for surface IDs, with no carve-out
(Section 5.1);
3. tightens `primary` and backward-compatibility semantics, resolving the
ambiguity previously left as Open Question 1 (Section 6);
4. relaxes the direct-open standalone requirement so it no longer conflicts
with the new attachment mechanism (Section 10);
5. adds an explicit URL-resolution algorithm for the surface `url` field
(new Section 6.3).
---
## 1. Purpose
Step 5 closed the XZBT-NGN MVP around a single implicit assumption: one
exhibit has one renderable visual surface, shown in the one iframe the host
loads.
Step 6 removes that assumption. An XZBT-compatible exhibit may have more than
one full-screen visual presentation — a main interface, an observation
display, an information wall, a second dome view — that are all views onto
**one logical exhibit instance and one authoritative state**, not independent
exhibits.
This document defines:
- what a presentation surface is and is not;
- how an exhibit advertises its surfaces;
- the generic mechanism by which a surface document attaches to the one
authoritative exhibit state, regardless of exhibit implementation;
- how surfaces stay synchronized to that one state;
- the lifecycle a surface goes through;
- who owns what (exhibit / NGN / transport / display endpoint);
- whether this requires a new contract version;
- what remains explicitly out of scope (Step 7).
It does not define casting, remote displays, or any display-transport
mechanism. Those are Step 7.
---
## 2. Definitions
**Logical exhibit instance.** One running exhibit session, with one
`sessionId`, one `stateRevision` history, and one authoritative set of
contract-visible persistent state values, as already defined by Contract 5.2
§13–§14. Multi-surface support does not change this definition; it depends on
it remaining singular.
**Presentation surface.** A renderable, full-screen(-capable) visual view of
the logical exhibit instance, addressable by a stable identifier, that an
exhibit advertises as independently viewable. A surface displays a coherent
subset (or the whole) of the exhibit's presentation, driven by the same
underlying state and the same contract session as every other surface of the
same exhibit.
**Surface descriptor.** The machine-readable metadata an exhibit publishes
describing one presentation surface: identifier, label, lifecycle-relevant
metadata, and enough information for a generic host to open it without any
exhibit-specific knowledge.
**Exhibit State Core.** The single in-exhibit authority for contract-visible
state, mutation, and event fan-out. Every exhibit already has exactly one of
these today, whether or not it is named as such — it is whatever internal
object/module the existing contract adapter (e.g.
`test-fixtures/reference-exhibits/shared/contract-core.js`) reads and writes.
Multi-surface support does not introduce a new core; it requires that every
surface, however many documents or windows are involved, attach to that one
existing core rather than instantiate a second one (Section 7.2).
**Control UI.** The operator-facing interface NGN renders from `describe`
(targets, state, events). It is not a presentation surface. It may run
alongside any number of open presentation surfaces.
**Display endpoint (Step 7, referenced only for contrast).** Where a surface
is physically or logically shown — a local browser window, a monitor, a
casting receiver. Step 6 defines *what* can be shown; Step 7 defines *where*.
---
## 3. Surface vs. Target Distinction
Contract 5.2 already has a generic extensibility mechanism: targets
(state/range/selection/impulse) discovered through `describe`. It is tempting
to model a surface as a `state`-kind target (e.g. `surface.observation.open:
boolean`). This proposal rejects that approach for surfaces themselves, for
four reasons:
1. **A target represents a piece of exhibit state or an action on it.** A
surface is neither — it is a *view*. Modeling "is this surface open" as
exhibit state conflates NGN-side presentation bookkeeping with exhibit
domain state, which Contract §2 explicitly prohibits ("The contract MUST
NOT be expanded merely to move Exhibit Engine responsibilities into the
exhibit").
2. **Targets have no place to carry surface-specific metadata** a generic
host needs to *render a window* rather than *render a control* — no field
in the base or kind-specific descriptors (§9) accommodates a document URL,
an aspect ratio hint, or a role.
3. **A target-based encoding would force every exhibit author to hand-roll an
ad hoc convention** (e.g. a `surface.*` naming prefix) for something that
should be a first-class, uniformly validated concept — exactly the kind of
silent protocol behavior the Step 6 brief prohibits inventing.
4. **Surfaces are discovered once per registry revision, not read/written
like state.** Their lifecycle (open/visible/hidden/closed) is host-side
presentation bookkeeping, not a `set`/`invoke` operation on exhibit state.
Surfaces are therefore modeled as a **new, parallel discovery construct**:
a `surfaces` array returned by `describe`, structurally independent of
`targets` and `capabilities`, but discovered through the same message and
governed by the same `registryRevision`.
**What is NOT a presentation surface:**
- the control/admin UI NGN renders from targets — that is an NGN-owned
concept and never appears in `surfaces`;
- a capability (§17) — capabilities describe whether a subsystem is usable,
not whether a view exists;
- a target of any kind — including a hypothetical `kind: "surface"` target,
which this proposal does not introduce;
- a display endpoint — monitors, browser windows, casting receivers are
Step 7 and are never named inside a surface descriptor;
- a scenario, recording, or packaging artifact.
---
## 4. Ownership Model
| Concern | Owner |
| --- | --- |
| What surfaces exist, their identifiers, labels, and descriptors | **Exhibit** |
| The actual rendering of each surface (HTML/CSS/JS/canvas/audio) | **Exhibit** |
| One authoritative state, `stateRevision`, and event stream (the Exhibit State Core) | **Exhibit**, shared by all surfaces of that exhibit |
| How surface documents attach to the Exhibit State Core internally | **Exhibit** (Section 7.2 defines the generic pattern; the specific transport choice is an exhibit implementation decision) |
| Discovering surfaces from `describe` and reacting to `registry.changed` | **NGN** |
| Deciding which surfaces are currently open and where | **NGN** (Step 6: local rendering only) |
| Routing a native in-surface interaction back into one canonical mutation path | **Exhibit internally**, using the same contract-visible state mechanism regardless of which surface originated the interaction |
| Transport connecting NGN's contract session to the exhibit | **Exhibit + transport binding**, unchanged from Contract §4 |
| Where a surface is physically displayed (monitor, window, cast target) | **Step 7 — out of scope here** |
The exhibit remains the single owner of truth. NGN never maintains a second
copy of exhibit state per surface; it discovers surfaces, opens/closes their
presentation, and continues to observe the one session's state and events
exactly as it does today. This is the direct application of Contract §2: the
exhibit exposes capabilities and state, NGN decides how to coordinate
presentation of them.
---
## 5. Surface Descriptor Proposal
### 5.1 Minimum required fields
```json
{
"id": "surface.control",
"label": "Control Room",
"kind": "surface",
"primary": true,
"url": "control.html"
}
```
| Field | Requirement | Notes |
| --- | --- | --- |
| `id` | REQUIRED string | **MUST conform exactly to the canonical target-ID grammar in Contract §8.1** — lowercase ASCII letters, digits, hyphens, and dots; begins with a lowercase letter; one or more dot-separated segments; no whitespace; no empty segments; recommended grammar `[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+`. No surface-specific carve-out exists: a bare single-segment ID (e.g. `"observation"`) is **not** valid, exactly as it would not be valid for a target ID. Authors are expected to namespace surface IDs meaningfully (e.g. `surface.control`, `surface.observation`, or an exhibit-specific namespace such as `gallery.artifact-wall`), the same discretion Contract §8 already gives target authors. Stability rule of §8.2 applies identically: once published, a surface ID is part of the exhibit's compatibility surface and SHOULD NOT be renamed casually. |
| `label` | REQUIRED string | Human-readable, for generic host display. |
| `kind` | REQUIRED, constant `"surface"` | Distinguishes this array's entries from targets/capabilities defensively, even though they already live in a separate array; makes the descriptor self-describing if ever handled outside its parent array. |
| `primary` | REQUIRED boolean | See Section 6.2 for the exact structural invariant and its enforcement. |
| `url` | REQUIRED string | See Section 6.3 for the full resolution algorithm. Summary: a same-origin, relative reference (path, and/or query/hash) resolved against the exhibit's own base URL — never an absolute or cross-origin URL. |
### 5.2 Optional fields (useful, not yet mandatory)
| Field | Purpose |
| --- | --- |
| `description` | Longer human-readable text for admin display. |
| `role` | Free-text or small controlled vocabulary hint (e.g. `"control"`, `"ambient"`, `"information"`) for host UI grouping. Advisory only; no contract behavior depends on it in 5.3. |
| `aspectRatio` | e.g. `"16:9"`. Advisory hint for window sizing. |
| `category` | Mirrors target `category` (§9) for consistent grouping conventions across the contract. |
| `requires` | Array of capability IDs (§17), same semantics as target `requires`: the surface remains discoverable but SHOULD be presented as degraded/unavailable if a required capability is not `ready`. |
### 5.3 Explicitly excluded fields
No field in the surface descriptor may name a display transport, a casting
protocol, a network endpoint, a device class, or a rendering target other than
"a document this host can load." This is the Step 6/Step 7 boundary enforced
at the schema level, not just by convention.
---
## 6. Discovery Proposal
Surfaces are discovered exactly where targets and capabilities are discovered:
in the `describe.result` response, as a new top-level array:
```json
{
"xzbt": "5.3",
"type": "describe.result",
"requestId": "req-010",
"sessionId": "sess-7f2a",
"exhibit": { "product": "museum-gallery", "version": "0.1.0", "build": "dev" },
"contract": { "major": 5, "minor": 3 },
"registryRevision": 1,
"stateRevision": 4,
"capabilities": [],
"targets": [],
"surfaces": [
{ "id": "surface.control", "label": "Control Room", "kind": "surface", "primary": true, "url": "control.html" },
{ "id": "surface.artifact", "label": "Artifact Display", "kind": "surface", "primary": false, "url": "artifact.html" },
{ "id": "surface.info-wall", "label": "Information Wall", "kind": "surface", "primary": false, "url": "info-wall.html" }
]
}
```
- `surfaces` is governed by `registryRevision`, identically to `targets`. A
`registry.changed` event MUST cause NGN to re-run `describe`, which
re-reads the current surface set along with the current target set. There
is no separate "surface revision" counter — one registry, one revision,
covering both targets and surfaces (Section 9).
### 6.1 Malformed individual entries
A malformed *individual* surface entry (missing `id`/`label`/`url`, invalid
`id` grammar per §8.1, non-relative or unresolvable `url` per Section 6.3) is
handled the same way a malformed target entry is already handled: the host
SHOULD skip only that entry and log a diagnostic, rather than discarding the
entire `surfaces` array. This mirrors the existing pattern in
`src/validation.js` and must be proven, not merely asserted, in Phase 6.3.
### 6.2 `primary` — structural invariant and backward compatibility
This is tightened relative to the prior revision to remove ambiguity.
**Three, and only three, recognized forms of `surfaces`:**
1. **Field absent.** The exhibit does not advertise multi-surface support at
all — an ordinary Contract 5.2 exhibit, or a 5.3 exhibit that has not
adopted the feature. A 5.3-aware host MUST behave exactly as NGN does
today: treat the exhibit's currently-loaded document/frame as one
implicit primary surface, with no surface picker UI, no `surfaces`
validation, and no behavior change from Step 5. This is the sole
legacy/compatibility path and needs no special-casing beyond "the field
isn't there."
2. **Field present and empty (`[]`).** Treated **identically** to the field
being absent (form 1). This removes the ambiguity left open in the prior
revision: an exhibit MAY emit `surfaces: []` (e.g. because it computes the
field programmatically) without that being treated as an error or as
"zero surfaces available." A host MUST NOT distinguish `[]` from absence.
3. **Field present and non-empty.** The array MUST contain **exactly one**
entry with `primary: true`. This is a *structural* invariant over the
whole array, not a per-entry validation concern (unlike the field-level
checks in Section 6.1). If the array contains zero `primary: true`
entries, or more than one, the **entire `surfaces` array is rejected as
malformed** — not just the offending entries — and the host falls back to
form 1 behavior (implicit single primary surface = current frame), logging
one clear diagnostic identifying the exhibit and the violation. This
fallback exists specifically so a broken multi-surface descriptor
degrades to "acts like Step 5" rather than to an undefined or partially
rendered surface list.
**Why this matters:** `primary` is what lets a 5.3-aware host that has *not*
been updated with any surface-picker UI still do the right thing by default
(open the primary surface, ignore the rest) — and it is what tells a fully
updated host which surface to treat as "the" exhibit view when no operator
choice has been made yet. Exactly one candidate for that role must exist
whenever `surfaces` claims to describe more than nothing.
### 6.3 URL resolution
`url` MUST be one of:
- a path relative to the exhibit's own base document location — the same
base URL NGN already resolved to load the exhibit's primary document in
the first place (i.e., the same resolution basis `src/connection.js` uses
today for the initial exhibit URL, not NGN's own admin-page location, and
not the requesting surface's future location);
- such a relative path with an appended query string and/or fragment
(`index.html?view=observation`, `control.html#panel-2`);
- a bare query string and/or fragment with no path segment
(`#observation`, `?surface=observation`), for a single-page exhibit where
surfaces are views within one already-served document rather than separate
files. In this form, resolution yields the exhibit's own base document URL
with that query/fragment applied.
**Resolution algorithm:**
1. Start from the exhibit's currently-established base URL — the same
absolute URL already used to load the exhibit and already validated by
`src/connection.js`'s same-origin/path-containment checks.
2. Resolve `url` against that base using standard relative-URL resolution.
3. The resolved absolute URL MUST remain same-origin with the base (Contract
§25's same-origin requirement extended to surfaces).
4. The resolved path component MUST pass the same path-containment check
`server/serve.js` already applies to the primary exhibit path (confined to
`public/`, `src/`, or `test-fixtures/`, no `..` escape).
5. `url` values that are absolute (`https://…`), protocol-relative
(`//…`), or resolve cross-origin or outside the served roots MUST be
rejected at step 3 or 4 as a malformed individual entry (Section 6.1) —
they do not invalidate the rest of the array.
This makes explicit what was previously only stated as a prohibition:
surface URLs are resolved on exactly the same basis, and validated by
exactly the same containment logic, as the primary exhibit document is
today. No new resolution concept is introduced.
---
## 7. State Synchronization Model
This is the section the whole feature stands or falls on, per the Step 6
brief's critical architectural rule.
### 7.1 One session, one state, many views
A surface does **not** open its own contract session. All surfaces of one
exhibit instance share the exhibit's single `sessionId`, single
`stateRevision` history, and single event `sequence` stream, exactly as
defined in Contract §5, §14, and §16 today. Multi-surface support adds zero
new session or state-authority concepts — it only adds more places that
*read* the one existing truth and more places a native interaction can
*originate from*.
### 7.2 The generic surface-attachment mechanism
Revision 1 left this as an unresolved implementation choice. This revision
defines a concrete, generic pattern every multi-surface exhibit is expected
to implement, independent of how many documents or windows are involved.
**The pattern has two required properties, one required sequence, and two
permitted transport shapes.**
**Required properties:**
- Exactly one Exhibit State Core exists per exhibit instance (Section 2).
Every surface attaches to that one Core. No surface ever instantiates a
second Core, a cached copy of state, or an independent computation of
state.
- Attachment is a runtime relationship between a surface document and the
Core, entirely internal to the exhibit. It is not part of the XZBT Exhibit
Contract and does not appear in Contract 5.3 — the contract only needs to
know that surfaces exist and how NGN opens one (Section 6). This mirrors
Contract §4's transport independence: the contract defines semantics, not
one mandatory internal wiring.
**Required attachment sequence** (identical regardless of transport shape
below):
1. The surface document loads and requests attachment from the Core.
2. The Core responds with the current authoritative state snapshot — sourced
from the same internal read path the contract adapter itself already uses
to answer `state.get`, not a second computation — and the current
`stateRevision`.
3. The Core registers the surface as a subscriber for ongoing
`state.changed` / `selection.changed` / relevant event notifications.
4. Any native interaction originating in the surface calls the Core's one
canonical mutation entry point (Section 7.4) — the same entry point a
contract `set`/`invoke` from NGN already uses.
5. On detach (surface closed), the Core drops the subscription. No state is
held by the surface, so nothing needs to be reconciled or discarded.
**Two permitted transport shapes for attachment** (an exhibit MUST implement
at least one; which one is an exhibit implementation decision, not a
contract requirement):
- **In-process attachment.** The surface is a view rendered within the same
JavaScript runtime/document that owns the Core — e.g. a single-page
exhibit that swaps visible panels, or a document that embeds other
surfaces as same-document components. Attachment is a direct in-memory
subscription; no cross-document transport is involved.
- **Cross-document attachment.** The surface runs in a separate browsing
context (its own window/tab, its own `url` per Section 6.3) and attaches
to the Core over a same-origin, exhibit-internal channel — e.g.
`BroadcastChannel`, a `SharedWorker`, or same-origin `postMessage` to
whichever document hosts the Core (typically the primary surface's
document). This channel is entirely separate from, and must not be
confused with, the NGN↔exhibit contract transport (Contract §4); it
carries no contract envelope and is never observed by NGN.
The Museum Gallery reference exhibit (Phase 6.2) is required to implement and
prove one concrete instance of this pattern — expected to be cross-document
attachment, since its surfaces are separate documents/windows opened by NGN
(Section 8) — and to document it as the reusable template Phase 6.7 adapts
for SciFi-XZBT's Observation surface. Phase 6.2 MAY also demonstrate
in-process attachment if useful for the control surface specifically, but at
least one cross-document instance must exist to prove the multi-window case
the brief requires.
### 7.3 How a surface receives ongoing state changes
Covered by the attachment sequence in 7.2 (steps 23): a surface never
polls; it subscribes at attachment time and receives the same
`state.changed`/`selection.changed` notifications the Core already emits
internally, over whichever transport shape (in-process or cross-document)
its attachment uses.
### 7.4 Native interaction routing (the canonical mutation path)
A user acting on any surface — clicking a control on the "Artifact Display,"
adjusting a slider on the "Information Wall" — MUST result in exactly the
same internal mutation call the exhibit's existing single-surface UI would
make, which is the same call the contract adapter uses to satisfy an
NGN-issued `set`/`invoke`. There must be exactly **one** function/method per
mutable target that changes exhibit state, on the Exhibit State Core itself;
every UI element on every surface, and every contract `set`/`invoke` from
NGN, calls into that same function. This is not new architecture — it is the
existing single-authoritative-state requirement from Contract §14, restated
for the case where more than one DOM document can trigger it.
Concretely for the reference exhibit: the same `contract-adapter.js` /
`exhibit.js` core already used by the Step 5 reference fixtures (see
`test-fixtures/reference-exhibits/shared/contract-core.js`) is the pattern to
extend into a proper Exhibit State Core, not replace. A surface's document is
a thin presentation layer attached to that core; it must not contain a
second, parallel copy of state or mutation logic.
### 7.5 What "no duplicate authoritative state" rules out
Explicitly disallowed by this model, matching the brief's stated
anti-pattern:
- launching a separate exhibit instance (a second `hello`/session with its
own `stateRevision` history) per surface;
- a surface document that keeps its own local copy of state and only
periodically reconciles it, rather than attaching per Section 7.2;
- a surface whose native interactions call an exhibit-internal function that
bypasses the Core's one canonical mutation entry point;
- two surfaces attaching to two different Core instances of the same
exhibit, however attachment is transported.
---
## 8. Lifecycle
A surface descriptor's existence in `surfaces` only establishes that the
surface is **available**. Whether it is currently shown is host-side,
per-surface, per-session bookkeeping — not exhibit state, and not persisted
across a full exhibit reload.
| State | Meaning | Who tracks it |
| --- | --- | --- |
| `available` | The surface is discoverable via `describe` but no host action has been taken on it. | Derived from `surfaces` array; not separately tracked. |
| `opened` | NGN has loaded the surface's document (Step 6: in a local browser window/view), which then performs the attachment sequence in Section 7.2. | NGN, per session. |
| `visible` | The opened surface's window/view currently has visual presence (not minimized/backgrounded). Best-effort; browsers do not always expose this reliably. | NGN, best-effort. |
| `hidden` | Opened but not currently visible (backgrounded, minimized). | NGN, best-effort. |
| `closed` | NGN has torn down the surface's window/view; the surface's document detaches from the Core (Section 7.2, step 5). The surface remains `available` for reopening. | NGN, per session. |
| `unavailable` | A `requires` capability (Section 5.2) is not `ready`. The surface stays listed but SHOULD be presented as non-operable. | NGN, derived from `capabilities`. |
Lifecycle transitions are entirely local bookkeeping on the NGN side about
*presentation*, not contract-visible exhibit state. Opening, closing, or
reopening a surface MUST NOT itself cause a `set`/`invoke`, MUST NOT change
`stateRevision`, and MUST NOT be recorded as an `action.executed` event. It
may optionally be logged as an NGN-local diagnostic entry, exactly as
`src/host.js` already logs connection lifecycle events today.
**Reopen behavior.** When a previously-closed surface is opened again, its
document performs the same attachment sequence as a first open (Section
7.2). Because state lives in the Core, not in the surface, a reopened
surface reflects current state automatically — there is nothing to "restore"
at the surface level.
---
## 9. Session/Revision Implications
- `registryRevision` covers `surfaces` in addition to `targets`/`capabilities`.
No separate surface-registry counter is introduced. A `registry.changed`
event already means "re-run `describe`"; that now also refreshes the known
surface set.
- `stateRevision` and event `sequence` are entirely unaffected by
multi-surface support — they remain properties of the one session, per
Contract §14 and §16, regardless of how many surfaces are open or how they
attach to the Core.
- **Reconnect.** On session loss and reconnect (existing `src/connection.js`
behavior), NGN re-runs the full negotiation and rediscovery, which
naturally re-obtains the current `surfaces` array. Any currently-open
surface windows are host-side presentation state and are NOT automatically
torn down by a reconnect; whether they should be is a Phase 6.5/6.6
implementation question (recommend: leave open, let the surface's own
document's attachment to the Core recover independently, rather than NGN
force-closing surface windows on transient session loss).
- **Are surface definitions static per registry revision?** Yes. Within one
`registryRevision`, the `surfaces` array is a stable snapshot, exactly like
`targets`. An exhibit that wants to change its surface set (add/remove a
surface) MUST bump `registryRevision` and emit `registry.changed`, per the
existing §23 mechanism — no new mechanism needed.
---
## 10. Standalone Behavior
A standalone exhibit — opened directly in a browser tab, with no NGN attached
— MUST continue to work with zero dependency on this model, per Contract §2's
governing rule. This revision relaxes the prior draft's requirement that
*every* surface be independently openable without any other document present,
because that conflicted with the cross-document attachment shape defined in
Section 7.2 (a surface attaching via `postMessage`/`BroadcastChannel` to
whichever document hosts the Core may genuinely need that document to already
be open).
The precise, tightened requirement:
- **The primary surface** (Section 6.2) MUST remain fully, unconditionally
standalone-capable: a person navigating directly to the exhibit's normal
entry point (its existing `index.html`) gets a complete, independently
usable experience with zero dependency on NGN, on any other surface being
open, or on any attachment sequence having occurred. This is the exhibit's
existing standalone guarantee, entirely unchanged by Step 6. Nothing about
standalone use of the primary surface requires a person to know surfaces
exist.
- **Non-primary surfaces** SHOULD remain directly openable without NGN
whenever the exhibit's chosen attachment mechanism allows it:
- an exhibit using **in-process attachment**, or a **cross-document**
mechanism where the surface can bootstrap its own Core connection
on demand (e.g. attaching to a `SharedWorker` that starts itself), can
and should support a non-primary surface being opened first, with no
other document present — matching how SciFi-XZBT's existing Observation
view already behaves today;
- an exhibit using **cross-document attachment that depends on the primary
document already being open** (e.g. plain `postMessage` to a specific
existing window) MAY require the primary surface to be open first for a
non-primary surface to attach successfully. This is an accepted
consequence of an exhibit's internal transport choice (Section 7.2), not
a contract violation, and MUST NOT be worked around by having the
non-primary surface silently start a second Core.
- In either case, this is a same-origin, exhibit-internal dependency
between the exhibit's own documents — never a dependency on NGN. A
non-primary surface opened without NGN and without its dependency met
SHOULD degrade to a clear "waiting for exhibit" state rather than
silently diverging into independent state.
- Nothing about surface discovery itself requires network access, a host, or
any NGN-specific code inside the exhibit. `surfaces` is just more
`describe` metadata; an exhibit run standalone never calls `describe` on
itself.
---
## 11. Host (NGN) Behavior
- On `describe`, NGN parses `surfaces` alongside `targets`/`capabilities`
using the validation discipline in Sections 6.16.2: tolerate individually
malformed entries, but reject the whole array (falling back to implicit
single-primary behavior) on a `primary` structural violation.
- NGN's admin/control UI (`src/ui.js`) gains a generic, descriptor-driven
surface list — rendered from `label`/`role`/`primary`, never from any
hard-coded surface name. This is the direct analog of the existing generic
target-rendering rule in the NGN Implementation Plan §8; no exhibit
vocabulary may leak into `src/`.
- Opening a surface (Phase 6.4) means NGN loads that surface's resolved `url`
(Section 6.3) into a new local browser window/view, same-origin, using the
same hosting mechanism `server/serve.js` already provides for the primary
exhibit frame today.
- NGN's control UI and any number of open surface windows coexist; closing
or reloading a surface window never tears down the underlying exhibit
session, and disconnecting the exhibit session (existing Disconnect
behavior) SHOULD close any surface windows NGN opened, since they have
nothing left to observe.
- A single-surface exhibit (ordinary case, including every Step 5 reference
fixture, and every exhibit using discovery form 1 or 2 from Section 6.2)
continues to work with **zero NGN behavior change**: one implicit or
declared primary surface, opened automatically into the one frame NGN
already loads today, with no surface picker UI shown when there is nothing
to pick between.
---
## 12. Non-Goals (Explicitly Out of Scope for Step 6)
Restating the brief's guardrails as binding scope boundaries for this
document and everything built from it:
- Casting, Chromecast, Google TV, Android clients, remote/network display
endpoints — all Step 7.
- Any encoding of a physical or network display target inside a surface
descriptor.
- MIDI, MCP, webhooks, scenarios, recording, telemetry acquisition,
multi-exhibit orchestration, databases, cloud services, user accounts.
- Any redesign of Contract 5.2 behavior unrelated to surfaces.
- Any surface concept that requires per-surface authentication/authorization
beyond the existing same-origin session trust boundary (§25) — internet-
facing auth remains NGN's problem generally, unchanged by this feature.
- Standardizing the cross-document attachment transport (`BroadcastChannel`
vs. `SharedWorker` vs. `postMessage`) as a contract-level choice — Section
7.2 deliberately leaves this to the exhibit.
---
## 13. Security / Origin Considerations
- Every surface `url` resolves same-origin per the algorithm in Section 6.3,
a direct extension of Contract §25's same-origin `postMessage` validation
requirement (`event.origin` and `event.source` checks) to the new case of
multiple documents. A surface document is exactly as trusted, and exactly
as constrained, as the exhibit's primary document already is.
- The exhibit-internal attachment channel (Section 7.2) — `BroadcastChannel`,
`SharedWorker`, or same-origin `postMessage` — inherits the same
same-origin trust boundary. It carries no contract envelope and MUST NOT be
reachable from any other origin; this is ordinary same-origin browser
isolation, not a new security mechanism.
- No surface descriptor field may contain executable markup, a remote origin,
or a URL scheme other than a relative path/fragment into the served
exhibit. This mirrors the existing safe-text-injection rule (§20) applied
to the new descriptor type.
- NGN's server (`server/serve.js`) already restricts served paths to
`public/`, `src/`, and `test-fixtures/` and denies path escapes; surface
`url` values are subject to the same path-containment validation (Section
6.3, step 4) before the host will load them — a malformed or escaping
`url` is a rejected individual entry (Section 6.1), not a followed one.
- Multiple surface documents observing one session is a same-origin,
same-trust-boundary fan-out, not a new external trust relationship. No new
authentication concept is introduced in Step 6.
---
## 14. Contract-Version Decision — APPROVED, NOT YET MINTED
### 14.1 Decision
**The owner has approved Contract 5.3 as the intended version for
multi-surface presentation.** This is no longer a pending recommendation; it
is the settled architectural direction this document and Phase 6.2 onward
are written against.
**What remains explicitly deferred, per this instruction, until separately
authorized:**
- minting the actual versioned file
(`docs/contract/XZBT-Exhibit-Contract-Specification-v5.3.md`);
- any change to `contract.major`/`contract.minor` reported by real
`describe.result` responses;
- any edit to the existing, currently-normative
`XZBT-Exhibit-Contract-Specification-v5.2.md` document.
This document (6.1) continues to illustrate `describe.result` examples as
`"xzbt": "5.3"` / `contract: {major: 5, minor: 3}` (Section 6) purely as the
**target shape** the eventual 5.3 document will formalize — this is
illustrative design content, not a claim that the file or the reported
version exists yet.
### 14.2 Why 5.3 (retained from Revision 1, unchanged)
`describe.result` gains a new top-level array, `surfaces`. This is additive
in the sense that an unaware consumer can ignore an unknown field — but
Contract §28.2 is explicit: *"Backward-compatible additions may increase the
minor version."* A new, normatively defined top-level response field with its
own validation rules (Section 6), its own descriptor grammar (Section 5), and
its own required/optional field contract is exactly the category of change
§28.2 describes, not a same-version clarification. Step 5's `arguments` field
addition (Contract §9.5) was explicitly scoped as *"clarifies Contract 5.2;
it does not introduce Contract 5.3"* — because it formalized behavior that
was already implicitly required (impulse arguments had to be validated
somehow) without adding a new discoverable construct. Surfaces are different:
nothing in 5.2 implies surfaces exist, and no 5.2-conformant exhibit or host
is required to know what `surfaces` means.
Contract major stays 5 — this is additive, not breaking. An exhibit that
only implements 5.2 behavior remains fully conformant; it simply never
emits `surfaces` (Section 6.2, form 1), and a 5.3-aware host tolerates that
identically to an exhibit that emits `surfaces: []` (form 2) or a single
`primary` entry (form 3). A 5.3-aware host MUST remain fully capable of
driving a 5.2-only exhibit exactly as it does today.
### 14.3 What still requires owner sign-off before the file is minted
- Timing: whether to mint the versioned file once this 6.1 design is
approved, or defer the formal document bump until the surface model has
additionally been proven out end-to-end in the reference exhibit (Phases
6.26.6).
- Any wording changes to the *existing* 5.2 document (this proposal
recommends none — 5.3 is additive-only — but that remains a decision to
reconfirm at minting time).
---
## 15. Open Questions
Revision 1's Open Question 1 (empty vs. absent `surfaces`) is now resolved by
Section 6.2 and removed from this list.
1. Should `role` (Section 5.2) start as free text or a small closed
enumeration? This proposal leaves it free text for 6.16.6 and defers
tightening it until real reference-exhibit and SciFi-XZBT usage exists to
generalize from.
2. Exact behavior when a currently-open surface's descriptor disappears from
a new `describe` result (e.g. `registryRevision` bump removes a surface
that is currently open in a window) — recommend NGN closes the window and
surfaces a diagnostic, but this needs to be proven in Phase 6.5/6.6, not
just asserted here.
3. Whether `requires` on a surface (Section 5.2) should block *opening* the
surface outright, or only annotate it as degraded while still allowing
open (consistent with how a target with an unavailable capability remains
discoverable per §9). This proposal recommends the latter, for
consistency, but flags it for confirmation during Phase 6.3.
4. Whether the generic attachment mechanism (Section 7.2) should eventually
be *documented* (not contractually mandated) as a recommended pattern in
the Authoring Guide, once the reference exhibit and SciFi-XZBT adaptation
have both proven it out — so future exhibit authors aren't reinventing
attachment transport choices from scratch. Recommend deferring this
documentation question to after Phase 6.8.
---
## 16. Acceptance Criteria for 6.1 (Revision 2)
This document satisfies Phase 6.1 when:
- [x] It defines what a presentation surface is and is not (Sections 23).
- [x] It defines the surface descriptor, required and optional fields
(Section 5), with surface IDs conforming exactly to the existing
canonical target-ID grammar (Section 5.1) — no carve-out.
- [x] It defines discovery as an extension to `describe` governed by
`registryRevision` (Sections 6, 9), with a tightened, unambiguous
`primary`/backward-compatibility invariant covering exactly three
recognized forms of `surfaces` (Section 6.2).
- [x] It defines an explicit, generic surface-attachment mechanism — required
properties, a required attachment sequence, and two permitted
transport shapes — that any exhibit implementation can follow without
inventing its own protocol (Section 7.2).
- [x] It defines an explicit URL-resolution algorithm for the surface `url`
field, including same-origin and path-containment enforcement (Section
6.3).
- [x] It defines a state-synchronization model with a single authoritative
state (the Exhibit State Core) shared by all surfaces, and explicitly
rules out per-surface duplicated state (Section 7).
- [x] It defines the surface lifecycle (Section 8).
- [x] It defines ownership across exhibit / NGN / transport / display
endpoint (Section 4), explicitly deferring display endpoints to Step 7.
- [x] It defines standalone-exhibit compatibility with a precise,
non-conflicting requirement split between the primary surface
(unconditional) and non-primary surfaces (best-effort, attachment-
mechanism-dependent) (Section 10).
- [x] It defines security/origin assumptions, extended to the attachment
channel (Section 13).
- [x] It records the owner-approved Contract 5.3 decision, with the file
mint and contract-document edits still explicitly deferred (Section
14).
- [x] It lists genuine remaining open questions rather than inventing answers
where none are warranted (Section 15).
- [x] It does not begin implementation, does not create the Contract 5.3
file, and does not modify existing source or contract documents.
**Next step, upon final owner approval of this revision:** mint the
versioned Contract 5.3 file per Section 14.3's timing decision, then begin
Phase 6.2 — build the Museum Gallery reference exhibit proving this model.
Neither has begun as part of this document.