—
+Lighting: --
+Rotation: --
+ +Attaching…
+diff --git a/Claude outputs/XZBT-Contract-5.2-to-5.3.diff b/Claude outputs/XZBT-Contract-5.2-to-5.3.diff new file mode 100644 index 0000000..c20e7c7 --- /dev/null +++ b/Claude outputs/XZBT-Contract-5.2-to-5.3.diff @@ -0,0 +1,594 @@ +--- /mnt/user-data/uploads/XZBT-NGN/docs/contract/XZBT-Exhibit-Contract-Specification-v5.2.md 2026-09-14 20:04:53.376627937 +0000 ++++ /home/claude/XZBT-Exhibit-Contract-Specification-v5.3.md 2026-09-14 20:19:48.228607469 +0000 +@@ -1,10 +1,14 @@ + # XZBT Exhibit Contract Specification +-## Version 5.2 ++## Version 5.3 + +-**Status:** Proposed normative specification +-**Document version:** 5.2 +-**Contract family:** XZBT Exhibit Contract +-**Compatibility major:** 5 ++**Status:** Proposed normative specification ++**Document version:** 5.3 ++**Contract family:** XZBT Exhibit Contract ++**Compatibility major:** 5 ++**Supersedes:** Version 5.2, additively. No normative text in Sections 1–30 ++below has been altered from 5.2 except the two explicitly-marked additions in ++Section 7 (the `surfaces` field reference) and Section 28 (subsection 28.8). ++See Section 39 for the full changelog. + **Primary consumers:** XZBT-compatible exhibits and XZBT-NGN Exhibit Engine + + --- +@@ -111,7 +115,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "hello", + "requestId": "req-001", + "host": { +@@ -126,7 +130,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "hello.result", + "requestId": "req-001", + "sessionId": "sess-7f2a", +@@ -137,13 +141,15 @@ + }, + "contract": { + "major": 5, +- "minor": 2 ++ "minor": 3 + } + } + ``` + + If no compatible major version exists, the exhibit MUST respond with an `error` carrying code `UNSUPPORTED_VERSION`, MUST echo the `requestId` when recoverable, and MUST NOT create a session. + ++A 5.2-only exhibit reporting `{"major": 5, "minor": 2}` remains fully compatible with a 5.3-aware host under the ordinary contract-major compatibility rule (Section 28.1); nothing in this handshake requires either party to implement 5.3-specific behavior. ++ + --- + + ## 6. Message Envelope +@@ -154,7 +160,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "invoke", + "requestId": "req-1042", + "sessionId": "sess-7f2a", +@@ -169,7 +175,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "invoke.result", + "requestId": "req-1042", + "sessionId": "sess-7f2a", +@@ -181,7 +187,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "error", + "requestId": "req-1042", + "sessionId": "sess-7f2a", +@@ -219,7 +225,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "describe", + "requestId": "req-010", + "sessionId": "sess-7f2a" +@@ -230,7 +236,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "describe.result", + "requestId": "req-010", + "sessionId": "sess-7f2a", +@@ -241,12 +247,13 @@ + }, + "contract": { + "major": 5, +- "minor": 2 ++ "minor": 3 + }, + "registryRevision": 1, + "stateRevision": 27, + "capabilities": [], +- "targets": [] ++ "targets": [], ++ "surfaces": [] + } + ``` + +@@ -256,6 +263,14 @@ + + NGN MUST discover targets instead of assuming that an exhibit exposes a fixed science-fiction vocabulary. + ++**`surfaces` (introduced in Contract 5.3).** An OPTIONAL array of presentation ++surface descriptors, normatively defined in Section 31. Its absence, or an ++empty array, both mean the exhibit has not adopted multi-surface presentation ++and is functionally equivalent to a Contract 5.2 `describe.result`, which ++never contained this field. A 5.3-aware host MUST treat a `describe.result` ++lacking `surfaces` identically to one where `surfaces` is present and empty ++(Section 31.5). ++ + --- + + ## 8. Canonical Target IDs +@@ -294,6 +309,9 @@ + [a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+ + ``` + ++This grammar also governs presentation surface identifiers (Section 31.2); ++no separate identifier grammar is defined for surfaces. ++ + ### 8.2 Stability + + Once published as part of an exhibit's external contract, a canonical target ID becomes part of that exhibit's compatibility surface. +@@ -405,6 +423,11 @@ + + `kind` is authoritative for invokability. No separate `invokable` field is defined. + ++Presentation surfaces (Section 31) are a structurally distinct descriptor ++family from targets and are never expressed using any target `kind`, ++including `impulse`. A host MUST NOT infer surface existence from any target ++descriptor. ++ + ### 9.5 Impulse arguments (authoritative Contract 5.2 clarification) + + Impulse descriptors use `arguments`, an array of argument descriptors. This +@@ -489,7 +512,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "invoke", + "requestId": "req-200", + "sessionId": "sess-7f2a", +@@ -514,7 +537,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "set", + "requestId": "req-201", + "sessionId": "sess-7f2a", +@@ -537,7 +560,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "state.get", + "requestId": "req-300", + "sessionId": "sess-7f2a" +@@ -548,7 +571,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "state.result", + "requestId": "req-300", + "sessionId": "sess-7f2a", +@@ -568,6 +591,11 @@ + + A state snapshot MUST NOT imply that every internal exhibit variable is externally exposed. + ++State exposed through `values` is scoped to the one logical exhibit instance ++and its one session, regardless of how many presentation surfaces (Section ++31) are currently open. `state.get` MUST NOT be parameterized by surface, and ++no surface-specific state view is defined by this contract. ++ + --- + + ## 14. State Revision and Mutation Transactions +@@ -588,6 +616,10 @@ + + All contract-visible state changes committed by one transaction share one resulting `stateRevision`. + ++A native interaction originating on any presentation surface (Section 31) is ++one mutation transaction, subject to this same rule, regardless of which ++surface it originated on. ++ + ### 14.2 Ordering + + The exhibit MUST: +@@ -608,6 +640,11 @@ + + The counters serve different purposes and MUST NOT be treated as interchangeable. + ++Multi-surface presentation does not introduce a second revision or sequence ++counter of any kind. Every presentation surface of one exhibit instance ++observes the same `stateRevision` history and the same event `sequence` ++stream defined here. ++ + --- + + ## 15. Source and Origin +@@ -632,6 +669,12 @@ + + This distinction is required for recording and feedback-loop prevention. + ++An interaction originating on a presentation surface other than the primary ++surface (Section 31.3) uses the same `source` vocabulary as an interaction on ++the primary surface — typically `ui`. This contract does not define a ++per-surface source value; which surface an interaction originated on is not ++contract-visible. ++ + --- + + ## 16. Events +@@ -653,7 +696,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "state.changed", + "sessionId": "sess-7f2a", + "sequence": 144, +@@ -669,7 +712,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "action.executed", + "sessionId": "sess-7f2a", + "sequence": 145, +@@ -710,6 +753,13 @@ + + It SHOULD NOT automatically record its own `scenario` or host playback events back into the scenario being recorded unless explicitly configured. + ++### 16.6 `registry.changed` and surfaces (Contract 5.3) ++ ++`registry.changed` (Section 23) covers changes to `surfaces` in addition to ++`targets` and capability metadata. A single `registryRevision` governs both; ++this contract does not define a separate surface-registry revision. See ++Section 31.6. ++ + --- + + ## 17. Capability Model +@@ -751,6 +801,10 @@ + + A host MUST treat the current discovered state as authoritative. + ++A presentation surface (Section 31) MAY declare `requires` against ++capability IDs defined here, using identical semantics to target `requires` ++(Section 9). ++ + --- + + ## 18. Speech +@@ -765,7 +819,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "invoke", + "requestId": "req-410", + "sessionId": "sess-7f2a", +@@ -843,6 +897,9 @@ + + Each text target SHOULD declare a maximum accepted length. + ++This restriction applies equally to any text rendered by a presentation ++surface (Section 31); surfaces introduce no new text-injection surface area. ++ + --- + + ## 21. Telemetry +@@ -899,7 +956,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "set", + "requestId": "req-520", + "sessionId": "sess-7f2a", +@@ -914,7 +971,7 @@ + + After lease expiration, the exhibit MAY return to simulated ownership. + +-The exact telemetry lease mechanism remains optional in 5.2. ++The exact telemetry lease mechanism remains optional in 5.2, unchanged in 5.3. + + A simple explicit release mechanism is also acceptable. + +@@ -932,6 +989,10 @@ + + Registry revision SHOULD NOT change merely because a fixed target becomes contextually unavailable. + ++As of Contract 5.3, `registryRevision` also governs the `surfaces` array ++(Section 31.6). One counter covers both; this contract does not define an ++independent surface-registry revision. ++ + --- + + ## 24. Error Codes +@@ -978,6 +1039,11 @@ + + Same-origin `postMessage` transports MUST validate both `event.origin` and `event.source` against the expected host relationship. + ++The same-origin requirement in this section extends to presentation surface ++resolution and to any exhibit-internal attachment channel used between an ++exhibit's own documents (Section 31.7). Neither introduces a new trust ++boundary beyond the one already defined here. ++ + --- + + ## 26. Scenario Independence +@@ -1056,6 +1122,16 @@ + + An exhibit may add targets without changing the contract version, provided existing target semantics remain compatible. + ++### 28.8 Contract 5.3 (presentation surfaces) ++ ++Contract 5.3 adds the optional `surfaces` field to `describe.result` ++(Section 7) and the normative Presentation Surfaces model (Section 31), per ++the rule in 28.2: this is a backward-compatible addition, not a breaking ++change. Contract major remains 5. An exhibit reporting `{major: 5, minor: 2}` ++is unaffected by this addition and remains fully conformant; a 5.3-aware host ++MUST continue to interoperate with it exactly as under Contract 5.2 (Section ++31.5). ++ + --- + + ## 29. Conformance Minimum +@@ -1076,6 +1152,11 @@ + + Speech, telemetry, MIDI, scenarios, Web3D, and any particular exhibit namespace are optional capabilities. + ++This conformance minimum is unchanged by Contract 5.3. Presentation surfaces ++(Section 31) are an optional capability of the same kind: an exhibit that ++implements none of Section 31 and never emits `surfaces` remains fully ++conformant, at either contract minor. ++ + --- + + ## 30. Architectural Summary +@@ -1089,3 +1170,222 @@ + **The contract defines how that capability is described, observed, and invoked.** + + **XZBT-NGN decides how to orchestrate and integrate it.** ++ ++Version 5.3 preserves this architecture without modification and adds exactly ++one optional capability class — presentation surfaces (Section 31) — built ++entirely on the existing session, state-revision, event, and registry-revision ++mechanisms defined above. ++ ++--- ++ ++## 31. Presentation Surfaces (introduced in Contract 5.3) ++ ++This section is new in Contract 5.3. It formalizes the multi-surface ++presentation model approved in XZBT-NGN's Step 6.1 architecture document ++(`docs/architecture/XZBT-Multi-Surface-Model-Step6.1.md`, Revision 2). Its ++scope is narrowly the discovery and identification of presentation surfaces ++and the contract-visible guarantees around them; it does not define display ++transport, casting, or remote endpoints (explicitly out of scope — see ++Section 31.9). ++ ++### 31.1 Definition ++ ++A **presentation surface** is a renderable, full-screen-capable visual view ++of one logical exhibit instance, addressable by a stable identifier, that an ++exhibit advertises as independently viewable. Every presentation surface of ++one exhibit instance is driven by that exhibit's one authoritative ++`stateRevision` history and one event `sequence` stream (Sections 13, 14, ++16); this contract defines no mechanism by which two surfaces of the same ++exhibit instance could observe divergent state. ++ ++A presentation surface is not a target (Section 9), not a capability ++(Section 17), and not the host's own control/administration interface. It ++MUST NOT be represented using any target `kind`. ++ ++### 31.2 Surface descriptor ++ ++When an exhibit advertises presentation surfaces, each entry in the ++`surfaces` array (Section 7) MUST be an object with the following fields: ++ ++| Field | Requirement | Notes | ++| --- | --- | --- | ++| `id` | REQUIRED string | MUST conform to the canonical target-ID grammar (Section 8.1). No separate identifier grammar is defined for surfaces. | ++| `label` | REQUIRED string | Human-readable. | ++| `kind` | REQUIRED, constant `"surface"` | Identifies the descriptor type. | ++| `primary` | REQUIRED boolean | Governed by Section 31.3. | ++| `url` | REQUIRED string | Governed by Section 31.4. | ++ ++The following fields are OPTIONAL: ++ ++| Field | Purpose | ++| --- | --- | ++| `description` | Longer human-readable text. | ++| `role` | Advisory grouping hint (e.g. `"control"`, `"ambient"`, `"information"`). No contract behavior depends on this field's value in Contract 5.3. | ++| `aspectRatio` | Advisory sizing hint (e.g. `"16:9"`). | ++| `category` | Mirrors target `category` (Section 9) for consistent grouping. | ++| `requires` | Array of capability IDs (Section 17), with identical semantics to target `requires`: the surface remains discoverable but SHOULD be presented as degraded or unavailable when a required capability is not `ready`. | ++ ++No field of a surface descriptor MAY reference a display transport, casting ++protocol, network endpoint, or device class (Section 31.9). ++ ++### 31.3 The `primary` invariant ++ ++`surfaces` (Section 7) has exactly three conformant forms: ++ ++1. **Absent.** The exhibit does not advertise multi-surface presentation. ++ Functionally equivalent to a Contract 5.2 `describe.result`. ++2. **Present and empty (`[]`).** MUST be treated identically to form 1 by a ++ conformant host. ++3. **Present and non-empty.** The array MUST contain exactly one entry with ++ `primary: true`. An array with zero or more than one `primary: true` ++ entry is malformed as a whole; a host encountering this MUST fall back to ++ treating `surfaces` as absent (form 1) and SHOULD report a diagnostic. ++ ++The `primary` surface, when `surfaces` is present and non-empty, is the ++surface a host with no surface-specific UI opens by default, and is the ++surface whose standalone-use guarantee is unconditional (Section 31.8). ++ ++### 31.4 URL resolution ++ ++`url` MUST be one of: ++ ++- a path relative to the exhibit's own base document location — the same ++ base URL already used to establish the exhibit's session; ++- such a relative path with an appended query string and/or fragment; ++- a bare query string and/or fragment, resolving against the exhibit's own ++ base document, for a single-page exhibit whose surfaces are views within ++ one already-served document. ++ ++A host resolving `url` MUST: ++ ++1. resolve it against the exhibit's already-established base URL, not ++ against the host's own administrative interface location; ++2. reject the entry (Section 31.5) if the resolved URL is not same-origin ++ with that base, per the same-origin requirement in Section 25; ++3. apply the same path-containment validation the host already applies to ++ the exhibit's primary document, if any such validation exists in that ++ host implementation. ++ ++Absolute, protocol-relative, or cross-origin `url` values MUST be rejected as ++malformed individual entries; they do not invalidate the rest of the array. ++ ++### 31.5 Validation and backward compatibility ++ ++A host MUST validate each surface entry independently. An entry missing a ++required field, using an invalid `id` (Section 8.1), or specifying a `url` ++that fails Section 31.4 MUST be rejected individually; the host SHOULD skip ++only that entry, log a diagnostic, and continue processing the remainder of ++`surfaces`, except for the structural `primary` violation in Section 31.3, ++which invalidates the whole array. ++ ++A host that does not implement any Section 31 behavior MAY safely ignore the ++`surfaces` field entirely; doing so is fully conformant with the ++`describe.result` schema, since the field is OPTIONAL (Section 7). ++ ++An exhibit implementing only Contract 5.2 behavior is unaffected: it never ++emits `surfaces`, and no 5.3-only requirement applies to it. ++ ++### 31.6 Registry governance ++ ++`surfaces` is governed by `registryRevision` (Section 23) exactly as ++`targets` is. There is no independent surface-registry counter. A ++`registry.changed` event (Section 16.6) requires the host to re-run ++`describe` and re-read both `targets` and `surfaces`. ++ ++### 31.7 State and interaction ++ ++All presentation surfaces of one exhibit instance share that instance's one ++session, one `stateRevision` history, and one event `sequence` stream ++(Sections 13, 14, 16); this contract defines no per-surface state channel ++and no per-surface session. How an exhibit internally propagates state to ++each surface's rendering code, and how it routes a surface-originated ++interaction back into its own state mutation logic, is an exhibit ++implementation detail outside this contract's normative scope — the contract ++requires only the observable outcome: one authoritative state, and any ++successful interaction on any surface behaves, from the contract's ++perspective, exactly like the equivalent `set`/`invoke` (Section 14.1). ++ ++An exhibit's internal mechanism for connecting a surface's rendering code to ++its own state, including any same-origin channel used between the exhibit's ++own documents, is not part of the message envelope defined in Section 6 and ++is never observed by the host. ++ ++### 31.8 Standalone behavior ++ ++Per the governing rule in Section 2, an exhibit's `primary` surface (Section ++31.3) — or, when `surfaces` is absent or empty, the exhibit's ordinary entry ++point — MUST remain fully and unconditionally usable standalone, with no ++dependency on XZBT-NGN, on a contract session, or on any other surface. ++ ++A non-primary surface SHOULD remain directly usable without XZBT-NGN. This ++contract does not require every non-primary surface to be usable in complete ++isolation from the exhibit's other documents; an exhibit MAY have a ++non-primary surface depend on another of its own documents being present, as ++an exhibit-internal implementation consequence of Section 31.7, provided that ++dependency is never on XZBT-NGN itself. ++ ++### 31.9 Non-goals ++ ++This section defines discovery and identification of presentation surfaces ++only. It does not define, and MUST NOT be extended by implementations to ++imply: ++ ++- casting or remote display protocols; ++- network display endpoints or device classes; ++- an assignment mechanism between a surface and a physical or logical ++ display; ++- any change to session, state, revision, or event semantics beyond the ++ cross-references added in Sections 7, 9.4, 13, 14.1, 14.4, 15, 16.6, 17, ++ 20, 23, 25, and 29 of this document. ++ ++Those concerns are reserved for future contract or XZBT-NGN work and are ++explicitly out of scope for Contract 5.3. ++ ++--- ++ ++## 32. Version 5.3 Summary ++ ++Contract 5.3 is Contract 5.2 plus one optional, additive capability: ++presentation surfaces (Section 31). No existing normative requirement is ++weakened, removed, or reinterpreted. An exhibit or host that implements ++nothing in Section 31 is unaffected and remains conformant. The architectural ++rule from Section 30 is unchanged: the exhibit exposes what it can do, the ++contract defines how that is described and invoked, and XZBT-NGN decides how ++to orchestrate it — now including, optionally, orchestrating which of an ++exhibit's several presentation surfaces is currently shown. ++ ++--- ++ ++## 39. Changelog (5.2 → 5.3) ++ ++This section exists only in 5.3 and has no 5.2 counterpart. ++ ++**Added:** ++ ++- `surfaces` OPTIONAL field on `describe.result` (Section 7). ++- Section 31, Presentation Surfaces: definition, descriptor schema, the ++ `primary` invariant and its three conformant forms, URL resolution, ++ validation/backward-compatibility rules, registry governance, the ++ state/interaction guarantee, standalone-use requirements, and explicit ++ non-goals. ++- Section 28.8, recording the versioning rationale for this addition. ++- Cross-reference sentences in Sections 5.2 (hello response commentary), ++ 9.4, 13, 14.1, 14.4, 15, 16.6, 17, 20, 23, 25, 29, and 30, each noting how ++ the existing normative rule in that section extends to, or is unaffected ++ by, presentation surfaces. None of these cross-references change the ++ normative requirement already stated in 5.2 for that section. ++ ++**Changed:** ++ ++- Header metadata (document version, compatibility statement). ++- Illustrative `"xzbt"` and `contract.minor` values in JSON examples updated ++ from `"5.2"` / `2` to `"5.3"` / `3` throughout, for internal consistency ++ within this document. This is cosmetic within the example payloads and ++ does not alter any example's normative meaning. ++ ++**Removed:** nothing. No 5.2 requirement is weakened, deleted, or ++reinterpreted by this document. ++ ++**Not changed:** Sections 1–4, 6, 8.2–8.3, 10–12, 18–19, 21–22, 24, 26–27, ++28.1–28.7 carry no 5.3-specific content and are reproduced from 5.2 ++unmodified except for the cosmetic example-version updates noted above. diff --git a/XZBT-Contract-5.2-to-5.3.diff b/XZBT-Contract-5.2-to-5.3.diff new file mode 100644 index 0000000..e626643 --- /dev/null +++ b/XZBT-Contract-5.2-to-5.3.diff @@ -0,0 +1,650 @@ +--- /mnt/user-data/uploads/XZBT-NGN/docs/contract/XZBT-Exhibit-Contract-Specification-v5.2.md 2026-09-14 20:04:53.376627937 +0000 ++++ /home/claude/XZBT-Exhibit-Contract-Specification-v5.3.md 2026-09-14 20:34:49.665940986 +0000 +@@ -1,10 +1,14 @@ + # XZBT Exhibit Contract Specification +-## Version 5.2 ++## Version 5.3 + +-**Status:** Proposed normative specification +-**Document version:** 5.2 +-**Contract family:** XZBT Exhibit Contract +-**Compatibility major:** 5 ++**Status:** Proposed normative specification ++**Document version:** 5.3 ++**Contract family:** XZBT Exhibit Contract ++**Compatibility major:** 5 ++**Supersedes:** Version 5.2, additively. No existing normative requirement ++from 5.2 has been removed, weakened, or reinterpreted. Contract 5.3 adds ++presentation-surface provisions and surface-related cross-references as ++documented in Section 39. + **Primary consumers:** XZBT-compatible exhibits and XZBT-NGN Exhibit Engine + + --- +@@ -111,7 +115,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "hello", + "requestId": "req-001", + "host": { +@@ -126,7 +130,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "hello.result", + "requestId": "req-001", + "sessionId": "sess-7f2a", +@@ -137,13 +141,15 @@ + }, + "contract": { + "major": 5, +- "minor": 2 ++ "minor": 3 + } + } + ``` + + If no compatible major version exists, the exhibit MUST respond with an `error` carrying code `UNSUPPORTED_VERSION`, MUST echo the `requestId` when recoverable, and MUST NOT create a session. + ++A 5.2-only exhibit reporting `{"major": 5, "minor": 2}` remains fully compatible with a 5.3-aware host under the ordinary contract-major compatibility rule (Section 28.1); nothing in this handshake requires either party to implement 5.3-specific behavior. ++ + --- + + ## 6. Message Envelope +@@ -154,7 +160,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "invoke", + "requestId": "req-1042", + "sessionId": "sess-7f2a", +@@ -169,7 +175,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "invoke.result", + "requestId": "req-1042", + "sessionId": "sess-7f2a", +@@ -181,7 +187,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "error", + "requestId": "req-1042", + "sessionId": "sess-7f2a", +@@ -219,7 +225,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "describe", + "requestId": "req-010", + "sessionId": "sess-7f2a" +@@ -230,7 +236,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "describe.result", + "requestId": "req-010", + "sessionId": "sess-7f2a", +@@ -241,12 +247,13 @@ + }, + "contract": { + "major": 5, +- "minor": 2 ++ "minor": 3 + }, + "registryRevision": 1, + "stateRevision": 27, + "capabilities": [], +- "targets": [] ++ "targets": [], ++ "surfaces": [] + } + ``` + +@@ -256,6 +263,14 @@ + + NGN MUST discover targets instead of assuming that an exhibit exposes a fixed science-fiction vocabulary. + ++**`surfaces` (introduced in Contract 5.3).** An OPTIONAL array of presentation ++surface descriptors, normatively defined in Section 31. Its absence, or an ++empty array, both mean the exhibit has not adopted multi-surface presentation ++and is functionally equivalent to a Contract 5.2 `describe.result`, which ++never contained this field. A 5.3-aware host MUST treat a `describe.result` ++lacking `surfaces` identically to one where `surfaces` is present and empty ++(Section 31.5). ++ + --- + + ## 8. Canonical Target IDs +@@ -294,6 +309,9 @@ + [a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+ + ``` + ++This grammar also governs presentation surface identifiers (Section 31.2); ++no separate identifier grammar is defined for surfaces. ++ + ### 8.2 Stability + + Once published as part of an exhibit's external contract, a canonical target ID becomes part of that exhibit's compatibility surface. +@@ -405,6 +423,11 @@ + + `kind` is authoritative for invokability. No separate `invokable` field is defined. + ++Presentation surfaces (Section 31) are a structurally distinct descriptor ++family from targets and are never expressed using any target `kind`, ++including `impulse`. A host MUST NOT infer surface existence from any target ++descriptor. ++ + ### 9.5 Impulse arguments (authoritative Contract 5.2 clarification) + + Impulse descriptors use `arguments`, an array of argument descriptors. This +@@ -489,7 +512,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "invoke", + "requestId": "req-200", + "sessionId": "sess-7f2a", +@@ -514,7 +537,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "set", + "requestId": "req-201", + "sessionId": "sess-7f2a", +@@ -537,7 +560,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "state.get", + "requestId": "req-300", + "sessionId": "sess-7f2a" +@@ -548,7 +571,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "state.result", + "requestId": "req-300", + "sessionId": "sess-7f2a", +@@ -568,6 +591,11 @@ + + A state snapshot MUST NOT imply that every internal exhibit variable is externally exposed. + ++State exposed through `values` is scoped to the one logical exhibit instance ++and its one session, regardless of how many presentation surfaces (Section ++31) are currently open. `state.get` MUST NOT be parameterized by surface, and ++no surface-specific state view is defined by this contract. ++ + --- + + ## 14. State Revision and Mutation Transactions +@@ -588,6 +616,10 @@ + + All contract-visible state changes committed by one transaction share one resulting `stateRevision`. + ++A native interaction originating on any presentation surface (Section 31) is ++one mutation transaction, subject to this same rule, regardless of which ++surface it originated on. ++ + ### 14.2 Ordering + + The exhibit MUST: +@@ -608,6 +640,11 @@ + + The counters serve different purposes and MUST NOT be treated as interchangeable. + ++Multi-surface presentation does not introduce a second revision or sequence ++counter of any kind. Every presentation surface of one exhibit instance ++observes the same `stateRevision` history and the same event `sequence` ++stream defined here. ++ + --- + + ## 15. Source and Origin +@@ -632,6 +669,12 @@ + + This distinction is required for recording and feedback-loop prevention. + ++An interaction originating on a presentation surface other than the primary ++surface (Section 31.3) uses the same `source` vocabulary as an interaction on ++the primary surface — typically `ui`. This contract does not define a ++per-surface source value; which surface an interaction originated on is not ++contract-visible. ++ + --- + + ## 16. Events +@@ -653,7 +696,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "state.changed", + "sessionId": "sess-7f2a", + "sequence": 144, +@@ -669,7 +712,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "action.executed", + "sessionId": "sess-7f2a", + "sequence": 145, +@@ -710,6 +753,13 @@ + + It SHOULD NOT automatically record its own `scenario` or host playback events back into the scenario being recorded unless explicitly configured. + ++### 16.6 `registry.changed` and surfaces (Contract 5.3) ++ ++`registry.changed` (Section 23) covers changes to `surfaces` in addition to ++`targets` and capability metadata. A single `registryRevision` governs both; ++this contract does not define a separate surface-registry revision. See ++Section 31.6. ++ + --- + + ## 17. Capability Model +@@ -751,6 +801,10 @@ + + A host MUST treat the current discovered state as authoritative. + ++A presentation surface (Section 31) MAY declare `requires` against ++capability IDs defined here, using identical semantics to target `requires` ++(Section 9). ++ + --- + + ## 18. Speech +@@ -765,7 +819,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "invoke", + "requestId": "req-410", + "sessionId": "sess-7f2a", +@@ -843,6 +897,9 @@ + + Each text target SHOULD declare a maximum accepted length. + ++This restriction applies equally to any text rendered by a presentation ++surface (Section 31); surfaces introduce no new text-injection surface area. ++ + --- + + ## 21. Telemetry +@@ -899,7 +956,7 @@ + + ```json + { +- "xzbt": "5.2", ++ "xzbt": "5.3", + "type": "set", + "requestId": "req-520", + "sessionId": "sess-7f2a", +@@ -914,7 +971,7 @@ + + After lease expiration, the exhibit MAY return to simulated ownership. + +-The exact telemetry lease mechanism remains optional in 5.2. ++The exact telemetry lease mechanism remains optional in 5.2, unchanged in 5.3. + + A simple explicit release mechanism is also acceptable. + +@@ -932,6 +989,10 @@ + + Registry revision SHOULD NOT change merely because a fixed target becomes contextually unavailable. + ++As of Contract 5.3, `registryRevision` also governs the `surfaces` array ++(Section 31.6). One counter covers both; this contract does not define an ++independent surface-registry revision. ++ + --- + + ## 24. Error Codes +@@ -978,6 +1039,11 @@ + + Same-origin `postMessage` transports MUST validate both `event.origin` and `event.source` against the expected host relationship. + ++The same-origin requirement in this section extends to presentation surface ++resolution and to any exhibit-internal attachment channel used between an ++exhibit's own documents (Section 31.7). Neither introduces a new trust ++boundary beyond the one already defined here. ++ + --- + + ## 26. Scenario Independence +@@ -1056,6 +1122,16 @@ + + An exhibit may add targets without changing the contract version, provided existing target semantics remain compatible. + ++### 28.8 Contract 5.3 (presentation surfaces) ++ ++Contract 5.3 adds the optional `surfaces` field to `describe.result` ++(Section 7) and the normative Presentation Surfaces model (Section 31), per ++the rule in 28.2: this is a backward-compatible addition, not a breaking ++change. Contract major remains 5. An exhibit reporting `{major: 5, minor: 2}` ++is unaffected by this addition and remains fully conformant; a 5.3-aware host ++MUST continue to interoperate with it exactly as under Contract 5.2 (Section ++31.5). ++ + --- + + ## 29. Conformance Minimum +@@ -1076,6 +1152,11 @@ + + Speech, telemetry, MIDI, scenarios, Web3D, and any particular exhibit namespace are optional capabilities. + ++This conformance minimum is unchanged by Contract 5.3. Presentation surfaces ++(Section 31) are an optional capability of the same kind: an exhibit that ++implements none of Section 31 and never emits `surfaces` remains fully ++conformant, at either contract minor. ++ + --- + + ## 30. Architectural Summary +@@ -1089,3 +1170,278 @@ + **The contract defines how that capability is described, observed, and invoked.** + + **XZBT-NGN decides how to orchestrate and integrate it.** ++ ++Version 5.3 preserves this architecture without modification and adds exactly ++one optional capability class — presentation surfaces (Section 31) — built ++entirely on the existing session, state-revision, event, and registry-revision ++mechanisms defined above. ++ ++--- ++ ++## 31. Presentation Surfaces (introduced in Contract 5.3) ++ ++This section is new in Contract 5.3. It formalizes the multi-surface ++presentation model approved in XZBT-NGN's Step 6.1 architecture document ++(`docs/architecture/XZBT-Multi-Surface-Model-Step6.1.md`, Revision 2). Its ++scope is narrowly the discovery and identification of presentation surfaces ++and the contract-visible guarantees around them; it does not define display ++transport, casting, or remote endpoints (explicitly out of scope — see ++Section 31.9). ++ ++### 31.1 Definition ++ ++A **presentation surface** is a renderable, full-screen-capable visual view ++of one logical exhibit instance, addressable by a stable identifier, that an ++exhibit advertises as independently viewable. Every presentation surface of ++one exhibit instance is driven by that exhibit's one authoritative ++`stateRevision` history and one event `sequence` stream (Sections 13, 14, ++16); this contract defines no mechanism by which two surfaces of the same ++exhibit instance could observe divergent state. ++ ++A presentation surface is not a target (Section 9), not a capability ++(Section 17), and not the host's own control/administration interface. It ++MUST NOT be represented using any target `kind`. ++ ++### 31.2 Surface descriptor ++ ++When an exhibit advertises presentation surfaces, each entry in the ++`surfaces` array (Section 7) MUST be an object with the following fields: ++ ++| Field | Requirement | Notes | ++| --- | --- | --- | ++| `id` | REQUIRED string | MUST conform to the canonical target-ID grammar (Section 8.1). No separate identifier grammar is defined for surfaces. | ++| `label` | REQUIRED string | Human-readable. | ++| `kind` | REQUIRED, constant `"surface"` | Identifies the descriptor type. | ++| `primary` | REQUIRED boolean | Governed by Section 31.3. | ++| `url` | REQUIRED string | Governed by Section 31.4. | ++ ++The following fields are OPTIONAL: ++ ++| Field | Purpose | ++| --- | --- | ++| `description` | Longer human-readable text. | ++| `role` | Advisory grouping hint (e.g. `"control"`, `"ambient"`, `"information"`). No contract behavior depends on this field's value in Contract 5.3. | ++| `aspectRatio` | Advisory sizing hint (e.g. `"16:9"`). | ++| `category` | Mirrors target `category` (Section 9) for consistent grouping. | ++| `requires` | Array of capability IDs (Section 17), with identical semantics to target `requires`: the surface remains discoverable but SHOULD be presented as degraded or unavailable when a required capability is not `ready`. | ++ ++No field of a surface descriptor MAY reference a display transport, casting ++protocol, network endpoint, or device class (Section 31.9). ++ ++### 31.3 The `primary` invariant and validation order ++ ++`surfaces` (Section 7) has exactly three conformant top-level forms: ++ ++1. **Absent.** The exhibit does not advertise multi-surface presentation. ++ Functionally equivalent to a Contract 5.2 `describe.result`. ++2. **Present and empty (`[]`).** MUST be treated identically to form 1 by a ++ conformant host. ++3. **Present and non-empty.** Evaluated using the deterministic validation ++ order below. ++ ++A host MUST evaluate a non-empty `surfaces` array in the following order: ++ ++1. Validate each individual surface descriptor against Section 31.2 and ++ Section 31.4. ++2. Discard individually invalid entries (Section 31.5); retain only the ++ valid entries as the working set. ++3. Evaluate the `primary` invariant against the working set, not against the ++ original array: ++ - If the working set is empty (every entry was individually invalid), ++ treat `surfaces` as if it were absent (form 1). ++ - If the working set contains exactly one entry with `primary: true`, ++ that entry is the primary surface and the working set is the ++ conformant surface catalog for this `describe.result`. ++ - If the working set contains zero entries with `primary: true`, or more ++ than one, the surface catalog as a whole is malformed: the host MUST ++ reject the entire working set (not just the offending entries), fall ++ back to treating `surfaces` as absent (form 1), and SHOULD report a ++ diagnostic. ++ ++This order is intentional and normative: the `primary` invariant is always ++evaluated after individually-invalid entries have already been discarded, so ++that one malformed entry cannot be mistaken for a missing or duplicate ++`primary` among otherwise-valid entries, and so that discarding a malformed ++entry can deterministically change whether the invariant holds. ++ ++The primary surface, once determined by this process, is the surface a ++Section-31-aware host that provides no operator surface selection opens by ++default, and is the surface whose standalone-use guarantee is unconditional ++(Section 31.8). ++ ++### 31.4 URL resolution ++ ++`url` MUST be one of: ++ ++- a path relative to the exhibit's own base document location — the same ++ base URL already used to establish the exhibit's session; ++- such a relative path with an appended query string and/or fragment; ++- a bare query string and/or fragment, resolving against the exhibit's own ++ base document, for a single-page exhibit whose surfaces are views within ++ one already-served document. ++ ++A host resolving `url` MUST: ++ ++1. resolve it against the exhibit's already-established base URL, not ++ against the host's own administrative interface location; ++2. reject the entry (Section 31.5) if the resolved URL is not same-origin ++ with that base, per the same-origin requirement in Section 25; ++3. apply the same path-containment validation the host already applies to ++ the exhibit's primary document, if any such validation exists in that ++ host implementation. ++ ++Absolute, protocol-relative, or cross-origin `url` values MUST be rejected as ++malformed individual entries; they do not invalidate the rest of the array. ++ ++### 31.5 Individual entry validation ++ ++A host MUST validate each surface entry independently, as step 1 of the ++order defined in Section 31.3. An entry missing a required field, using an ++invalid `id` (Section 8.1), or specifying a `url` that fails Section 31.4 is ++individually invalid; the host SHOULD skip only that entry, log a ++diagnostic, and continue evaluating the remainder of `surfaces`. Individual ++entry validation never by itself invalidates the whole array — only the ++structural `primary` evaluation performed afterward, against the surviving ++valid entries (Section 31.3, steps 2–3), can do that. ++ ++A host that does not implement any Section 31 behavior MAY safely ignore the ++`surfaces` field entirely; doing so is fully conformant with the ++`describe.result` schema, since the field is OPTIONAL (Section 7). ++ ++An exhibit implementing only Contract 5.2 behavior is unaffected: it never ++emits `surfaces`, and no 5.3-only requirement applies to it. ++ ++### 31.6 Registry governance ++ ++`surfaces` is governed by `registryRevision` (Section 23) exactly as ++`targets` is. There is no independent surface-registry counter. A ++`registry.changed` event (Section 16.6) requires the host to re-run ++`describe` and re-read both `targets` and `surfaces`. ++ ++### 31.7 State and interaction ++ ++All presentation surfaces of one exhibit instance share that instance's one ++session, one `stateRevision` history, and one event `sequence` stream ++(Sections 13, 14, 16); this contract defines no per-surface state channel ++and no per-surface session. How an exhibit internally propagates state to ++each surface's rendering code is an exhibit implementation detail outside ++this contract's normative scope. ++ ++The normative requirement on interaction is narrower than "every native ++interaction becomes a public target": any surface-originated interaction ++that changes contract-visible persistent state or executes a contract-visible ++action MUST obey the same mutation, revision, source, and event semantics ++(Sections 14, 15, 16) as the equivalent operation originating from the ++exhibit's primary UI or through a contract `set`/`invoke` (Section 14.1). A ++surface-local interaction that does not change contract-visible state and ++does not execute a contract-visible action — purely presentational ++interaction confined to that surface — remains exhibit-internal and is ++outside this contract's scope; this contract does not require it to have a ++corresponding target. ++ ++An exhibit's internal mechanism for connecting a surface's rendering code to ++its own state, including any same-origin channel used between the exhibit's ++own documents, is not part of the message envelope defined in Section 6 and ++is never observed by the host. ++ ++### 31.8 Standalone behavior ++ ++Per the governing rule in Section 2, an exhibit's `primary` surface (Section ++31.3) — or, when `surfaces` is absent or empty, the exhibit's ordinary entry ++point — MUST remain fully and unconditionally usable standalone, with no ++dependency on XZBT-NGN, on a contract session, or on any other surface. ++ ++A non-primary surface SHOULD remain directly usable without XZBT-NGN. This ++contract does not require every non-primary surface to be usable in complete ++isolation from the exhibit's other documents; an exhibit MAY have a ++non-primary surface depend on another of its own documents being present, as ++an exhibit-internal implementation consequence of Section 31.7, provided that ++dependency is never on XZBT-NGN itself. ++ ++### 31.9 Non-goals ++ ++This section defines discovery and identification of presentation surfaces ++only. It does not define, and MUST NOT be extended by implementations to ++imply: ++ ++- casting or remote display protocols; ++- network display endpoints or device classes; ++- an assignment mechanism between a surface and a physical or logical ++ display; ++- any change to session, state, revision, or event semantics beyond the ++ cross-references added in Sections 7, 9.4, 13, 14.1, 14.4, 15, 16.6, 17, ++ 20, 23, 25, and 29 of this document. ++ ++Those concerns are reserved for future contract or XZBT-NGN work and are ++explicitly out of scope for Contract 5.3. ++ ++--- ++ ++## 32. Version 5.3 Summary ++ ++Contract 5.3 is Contract 5.2 plus one optional, additive capability: ++presentation surfaces (Section 31). No existing normative requirement is ++weakened, removed, or reinterpreted. An exhibit or host that implements ++nothing in Section 31 is unaffected and remains conformant. The architectural ++rule from Section 30 is unchanged: the exhibit exposes what it can do, the ++contract defines how that is described and invoked, and XZBT-NGN decides how ++to orchestrate it — now including, optionally, orchestrating which of an ++exhibit's several presentation surfaces is currently shown. ++ ++--- ++ ++## 39. Changelog (5.2 → 5.3) ++ ++This section exists only in 5.3 and has no 5.2 counterpart. ++ ++**Added:** ++ ++- `surfaces` OPTIONAL field on `describe.result` (Section 7). ++- Section 31, Presentation Surfaces: definition, descriptor schema, the ++ `primary` invariant and its three conformant forms, URL resolution, ++ validation/backward-compatibility rules, registry governance, the ++ state/interaction guarantee, standalone-use requirements, and explicit ++ non-goals. ++- Section 28.8, recording the versioning rationale for this addition. ++- Cross-reference sentences in Sections 5.2 (hello response commentary), ++ 9.4, 13, 14.1, 14.4, 15, 16.6, 17, 20, 23, 25, 29, and 30, each noting how ++ the existing normative rule in that section extends to, or is unaffected ++ by, presentation surfaces. None of these cross-references change the ++ normative requirement already stated in 5.2 for that section. ++ ++**Changed:** ++ ++- Header metadata (document version, compatibility statement) — the ++ supersedes wording was tightened during the pre-6.2 cleanup pass to state ++ plainly that no existing 5.2 requirement was removed, weakened, or ++ reinterpreted, rather than characterizing which sections happened to ++ receive cross-references. ++- Illustrative `"xzbt"` and `contract.minor` values in JSON examples updated ++ from `"5.2"` / `2` to `"5.3"` / `3` throughout, for internal consistency ++ within this document. This is cosmetic within the example payloads and ++ does not alter any example's normative meaning. ++- Section 31.3 was restructured, during the same cleanup pass, into an ++ explicit, deterministic validation order (validate individual entries → ++ discard invalid ones → evaluate the `primary` invariant against the ++ surviving set) so that a discarded individually-invalid entry cannot leave ++ the `primary` determination ambiguous. Section 31.5 was narrowed to cover ++ only individual-entry validation and now cross-references 31.3 for the ++ structural `primary` evaluation, rather than restating it. This is a ++ clarification of Revision 2's intent, not a new rule. ++- Section 31.7's interaction requirement was narrowed from "every successful ++ surface interaction behaves like `set`/`invoke`" to the intended scope: ++ this requirement applies only to interactions that change contract-visible ++ state or execute a contract-visible action. Purely surface-local, ++ non-contract-visible interaction is explicitly out of scope and is not ++ required to have a corresponding target. ++- Section 31.3's reference to "a host with no surface-specific UI" was ++ reworded to "a Section-31-aware host that provides no operator surface ++ selection," to avoid it being read as covering a host that ignores Section ++ 31 entirely. ++ ++**Removed:** nothing. No 5.2 requirement is weakened, deleted, or ++reinterpreted by this document. ++ ++**Not changed:** Sections 1–4, 6, 8.2–8.3, 10–12, 18–19, 21–22, 24, 26–27, ++28.1–28.7 carry no 5.3-specific content and are reproduced from 5.2 ++unmodified except for the cosmetic example-version updates noted above. diff --git a/docs/architecture/XZBT-Multi-Surface-Model-Step6.1.md b/docs/architecture/XZBT-Multi-Surface-Model-Step6.1.md new file mode 100644 index 0000000..b000b67 --- /dev/null +++ b/docs/architecture/XZBT-Multi-Surface-Model-Step6.1.md @@ -0,0 +1,755 @@ +# 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 2–3): 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.1–6.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.2–6.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.1–6.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 2–3). +- [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. diff --git a/docs/contract/XZBT-Exhibit-Contract-Specification-v5.3.md b/docs/contract/XZBT-Exhibit-Contract-Specification-v5.3.md new file mode 100644 index 0000000..ff54537 --- /dev/null +++ b/docs/contract/XZBT-Exhibit-Contract-Specification-v5.3.md @@ -0,0 +1,1447 @@ +# XZBT Exhibit Contract Specification +## Version 5.3 + +**Status:** Proposed normative specification +**Document version:** 5.3 +**Contract family:** XZBT Exhibit Contract +**Compatibility major:** 5 +**Supersedes:** Version 5.2, additively. No existing normative requirement +from 5.2 has been removed, weakened, or reinterpreted. Contract 5.3 adds +presentation-surface provisions and surface-related cross-references as +documented in Section 39. +**Primary consumers:** XZBT-compatible exhibits and XZBT-NGN Exhibit Engine + +--- + +## 1. Purpose + +The XZBT Exhibit Contract defines a stable, discoverable language through which an external host can inspect, observe, and control an XZBT-compatible exhibit. + +The architecture is deliberately separated into three concerns: + +- **Exhibit:** presentation, native controls, audio, visuals, simulation, and exhibit-specific behavior. +- **Contract:** the shared control and observation language. +- **Exhibit Engine:** orchestration, recording, integration, networking, authoring, packaging, and administration. + +The contract does not require XZBT-NGN to exist for an exhibit to operate. + +An exhibit implementing this contract remains a complete independently usable artifact. + +--- + +## 2. Governing Architectural Rule + +An XZBT-compatible exhibit MUST remain operable without XZBT-NGN. + +Connection to XZBT-NGN is additive. + +The exhibit exposes addressable capabilities and state. + +XZBT-NGN decides how to coordinate, automate, integrate, record, schedule, and package those capabilities. + +The contract MUST NOT be expanded merely to move Exhibit Engine responsibilities into the exhibit. + +--- + +## 3. Roles + +### 3.1 Exhibit responsibilities + +An exhibit owns: + +- visual presentation; +- audio presentation; +- simulation and ambience; +- native UI; +- local input handling; +- internal state; +- exhibit-specific actions; +- standalone behavior; +- exhibit-native speech rendering; +- exhibit-native sound rendering; +- exhibit-native visual effects. + +### 3.2 Exhibit Engine responsibilities + +XZBT-NGN may: + +- discover an exhibit; +- inspect capabilities and targets; +- request state; +- invoke actions; +- set exposed values; +- observe normalized events; +- record operator activity; +- execute scenarios; +- author timelines; +- expose webhooks; +- manage authentication and authorization; +- connect external systems; +- map telemetry; +- package authored experiences; +- administer one or more exhibits. + +--- + +## 4. Transport Independence + +The contract defines semantics and normalized message envelopes, not one mandatory transport. + +Permitted transports may include: + +- same-origin `postMessage`; +- trusted wrapper bindings; +- local WebSocket transport; +- local application IPC; +- future host-specific bindings. + +The exhibit MUST NOT need to know whether a request originated from a webhook, Streamer.bot, simulator, telemetry source, administrative UI, scenario engine, or other integration. + +NGN translates external sources into contract operations. + +--- + +## 5. Session and Handshake + +A host MUST establish a contract session before sending control operations through a negotiated transport. + +A standalone exhibit MUST NOT depend on this handshake for normal operation. + +`hello` is the only contract message that MAY be sent without a `sessionId`. + +Every other request MUST carry a valid `sessionId` issued by the exhibit. Missing or unknown session identifiers MUST be rejected with `INVALID_SESSION`. + +### 5.1 Host hello + +```json +{ + "xzbt": "5.3", + "type": "hello", + "requestId": "req-001", + "host": { + "name": "XZBT-NGN", + "version": "0.1.0" + }, + "supportedContractMajors": [5] +} +``` + +### 5.2 Exhibit hello response + +```json +{ + "xzbt": "5.3", + "type": "hello.result", + "requestId": "req-001", + "sessionId": "sess-7f2a", + "exhibit": { + "product": "scifi-xzbt", + "version": "implementation-defined", + "build": "implementation-defined" + }, + "contract": { + "major": 5, + "minor": 3 + } +} +``` + +If no compatible major version exists, the exhibit MUST respond with an `error` carrying code `UNSUPPORTED_VERSION`, MUST echo the `requestId` when recoverable, and MUST NOT create a session. + +A 5.2-only exhibit reporting `{"major": 5, "minor": 2}` remains fully compatible with a 5.3-aware host under the ordinary contract-major compatibility rule (Section 28.1); nothing in this handshake requires either party to implement 5.3-specific behavior. + +--- + +## 6. Message Envelope + +Every request, response, and event transmitted through the contract MUST use a normalized envelope. + +### 6.1 Request + +```json +{ + "xzbt": "5.3", + "type": "invoke", + "requestId": "req-1042", + "sessionId": "sess-7f2a", + "target": "sfx.console-beep", + "args": {} +} +``` + +A `set` request MAY additionally contain an `options` object when the target explicitly declares supported options. + +### 6.2 Response + +```json +{ + "xzbt": "5.3", + "type": "invoke.result", + "requestId": "req-1042", + "sessionId": "sess-7f2a", + "ok": true +} +``` + +### 6.3 Error response + +```json +{ + "xzbt": "5.3", + "type": "error", + "requestId": "req-1042", + "sessionId": "sess-7f2a", + "ok": false, + "error": { + "code": "UNKNOWN_TARGET", + "message": "The requested target is not registered." + } +} +``` + +When a malformed message cannot be parsed well enough to recover `requestId` or `sessionId`, either field MAY be `null`. + +### 6.4 Correlation + +`requestId` MUST be unique within a host session. + +Responses MUST echo the corresponding `requestId`. + +Events are asynchronous and do not require a `requestId`, but SHOULD include a `correlationId` when directly caused by a specific request. + +### 6.5 Version field + +The `xzbt` field is advisory metadata for diagnostics. + +The negotiated session contract major/minor is authoritative after handshake. + +--- + +## 7. Describe + +The host MUST be able to request a complete machine-readable description. + +Request: + +```json +{ + "xzbt": "5.3", + "type": "describe", + "requestId": "req-010", + "sessionId": "sess-7f2a" +} +``` + +Response: + +```json +{ + "xzbt": "5.3", + "type": "describe.result", + "requestId": "req-010", + "sessionId": "sess-7f2a", + "exhibit": { + "product": "scifi-xzbt", + "version": "implementation-defined", + "build": "implementation-defined" + }, + "contract": { + "major": 5, + "minor": 3 + }, + "registryRevision": 1, + "stateRevision": 27, + "capabilities": [], + "targets": [], + "surfaces": [] +} +``` + +`describe` is authoritative for the current session. + +The `capabilities` array is a snapshot of current capability states at the time of the response. + +NGN MUST discover targets instead of assuming that an exhibit exposes a fixed science-fiction vocabulary. + +**`surfaces` (introduced in Contract 5.3).** An OPTIONAL array of presentation +surface descriptors, normatively defined in Section 31. Its absence, or an +empty array, both mean the exhibit has not adopted multi-surface presentation +and is functionally equivalent to a Contract 5.2 `describe.result`, which +never contained this field. A 5.3-aware host MUST treat a `describe.result` +lacking `surfaces` identically to one where `surfaces` is present and empty +(Section 31.5). + +--- + +## 8. Canonical Target IDs + +External contract target IDs use a canonical dotted namespace. + +Examples: + +```text +transport.playing +transport.muted +mix.master +view.observation +view.viewport-frame +event.alert-red +alert.active +sfx.console-beep +speech.say +telemetry.engine.temperature +``` + +### 8.1 Grammar + +A target ID MUST: + +- use lowercase ASCII letters, digits, hyphens, and dots; +- begin with a lowercase letter; +- contain one or more dot-separated segments; +- not contain whitespace; +- not contain empty segments; +- not be interpreted as a JavaScript property path. + +Recommended grammar: + +```text +[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+ +``` + +This grammar also governs presentation surface identifiers (Section 31.2); +no separate identifier grammar is defined for surfaces. + +### 8.2 Stability + +Once published as part of an exhibit's external contract, a canonical target ID becomes part of that exhibit's compatibility surface. + +Target IDs SHOULD NOT be renamed casually. + +Deprecated targets SHOULD remain discoverable long enough for hosts to migrate. + +### 8.3 Internal mapping and normalization + +An exhibit MAY map canonical external IDs to existing internal IDs. + +Example: + +```text +mix.master -> master-vol +``` + +Mapping is not always a rename. + +Example: + +```text +transport.playing -> absolute-state adapter around existing transport logic +``` + +An exhibit MUST NOT implement an externally visible persistent state target merely by blindly toggling an existing control. + +A conforming exhibit may need to build a normalization layer before its existing internals can satisfy the contract. + +--- + +## 9. Target Descriptor + +Each target returned by `describe` MUST include enough metadata for a generic host to inspect and operate it. + +Base descriptor: + +```json +{ + "id": "mix.master", + "label": "Master Volume", + "kind": "range", + "readable": true, + "writable": true, + "restorable": true, + "category": "mix", + "unit": null, + "requires": ["audio"] +} +``` + +`requires` references capability IDs from Section 17. + +When a required capability is not usable, the target remains discoverable but MUST NOT be operated successfully. A generic host SHOULD present it as unavailable rather than removing it. + +Additional fields depend on kind. + +### 9.1 Range + +```json +{ + "id": "mix.master", + "kind": "range", + "min": 0, + "max": 1, + "step": 0.01 +} +``` + +### 9.2 State + +```json +{ + "id": "view.observation", + "kind": "state", + "valueType": "boolean" +} +``` + +### 9.3 Selection + +```json +{ + "id": "universe.selected", + "kind": "selection", + "options": [ + {"value": "starfleet", "label": "Starflight Command"}, + {"value": "whataverse", "label": "Whataverse"} + ] +} +``` + +Values in examples are illustrative except where a profile document explicitly declares them normative. + +### 9.4 Impulse + +```json +{ + "id": "event.alert-red", + "kind": "impulse", + "readable": false, + "writable": false, + "restorable": false +} +``` + +A target with `kind: "impulse"` is invokable. + +`kind` is authoritative for invokability. No separate `invokable` field is defined. + +Presentation surfaces (Section 31) are a structurally distinct descriptor +family from targets and are never expressed using any target `kind`, +including `impulse`. A host MUST NOT infer surface existence from any target +descriptor. + +### 9.5 Impulse arguments (authoritative Contract 5.2 clarification) + +Impulse descriptors use `arguments`, an array of argument descriptors. This +clarifies Contract 5.2; it does not introduce Contract 5.3. Missing `arguments` +MUST be tolerated as an empty array for backward compatibility. New 5.2 exhibits +SHOULD explicitly emit `arguments: []` for argumentless impulses. + +```json +{ + "id": "speech.say", + "kind": "impulse", + "readable": false, + "writable": false, + "restorable": false, + "category": "speech", + "requires": ["speech"], + "arguments": [ + {"name": "text", "type": "string", "required": true, "maxLength": 2000} + ] +} +``` + +Each argument descriptor has: + +| Field | Requirement | +| --- | --- | +| `name` | REQUIRED string, unique within the target | +| `type` | REQUIRED: `string`, `number`, `integer`, or `boolean` | +| `required` | REQUIRED boolean | +| `label` | OPTIONAL human-readable string | +| `description` | OPTIONAL human-readable string | +| `enum` | OPTIONAL array of allowed literal values | +| `min`, `max` | OPTIONAL number | +| `step` | OPTIONAL positive number | +| `minLength`, `maxLength` | OPTIONAL non-negative integer | + +Invoke `args` MUST be an object. Every argument with `required: true` MUST be +present. Undeclared keys MUST be rejected with `INVALID_VALUE`. Values MUST match +the declared type, and all declared enum, bounds, step, and length constraints +MUST be enforced. + +NGN MAY generate controls directly from this metadata and MUST NOT infer +undocumented arguments. NGN MUST still surface an impulse with an unsupported +future argument type and report its schema as unsupported rather than inventing +behavior. + +Established examples: `speech.say` declares required string `text` with +`maxLength: 2000`; the established impulse form of `display.ticker` declares +required string `text` with `maxLength: 512`. Argumentless actions, including +`speech.generate-announcement` and argumentless event/sound impulses, declare +`arguments: []`. These examples do not establish a host target inventory. + +--- + +## 10. Target Kinds + +The base contract defines four kinds. + +### 10.1 State + +Persistent value such as boolean, string, or bounded scalar. + +### 10.2 Range + +Persistent bounded numeric state. + +### 10.3 Selection + +Persistent value selected from a declared set. + +### 10.4 Impulse + +Non-persistent action or event. + +Impulse targets MUST NOT appear in a restorable state snapshot as current state. + +--- + +## 11. Invoke + +`invoke` performs an impulse. + +```json +{ + "xzbt": "5.3", + "type": "invoke", + "requestId": "req-200", + "sessionId": "sess-7f2a", + "target": "sfx.console-beep", + "args": {} +} +``` + +The exhibit MUST validate the target and arguments before execution. + +Arguments MUST be validated against the declared target schema where such a schema exists. + +Unexpected argument keys SHOULD be rejected. + +A successful `invoke` response means that the operation was accepted for execution, not necessarily that a long-running effect has completed. + +--- + +## 12. Set + +`set` changes writable persistent state. + +```json +{ + "xzbt": "5.3", + "type": "set", + "requestId": "req-201", + "sessionId": "sess-7f2a", + "target": "mix.master", + "value": 0.75 +} +``` + +A successful state-changing set MUST result in a normalized state update. + +A successful no-op set MUST NOT increment `stateRevision`. + +--- + +## 13. State Snapshot + +The host MUST be able to request externally meaningful persistent state. + +Request: + +```json +{ + "xzbt": "5.3", + "type": "state.get", + "requestId": "req-300", + "sessionId": "sess-7f2a" +} +``` + +Response: + +```json +{ + "xzbt": "5.3", + "type": "state.result", + "requestId": "req-300", + "sessionId": "sess-7f2a", + "stateRevision": 28, + "values": { + "transport.playing": true, + "transport.muted": false, + "mix.master": 0.75, + "view.observation": true, + "universe.selected": "starfleet", + "preset.selected": "tng-bridge" + } +} +``` + +Only readable persistent targets belong in `values`. + +A state snapshot MUST NOT imply that every internal exhibit variable is externally exposed. + +State exposed through `values` is scoped to the one logical exhibit instance +and its one session, regardless of how many presentation surfaces (Section +31) are currently open. `state.get` MUST NOT be parameterized by surface, and +no surface-specific state view is defined by this contract. + +--- + +## 14. State Revision and Mutation Transactions + +The exhibit MUST maintain a monotonically increasing `stateRevision` for externally observable persistent state. + +### 14.1 Mutation transaction + +One top-level contract-visible mutation operation is one **mutation transaction**, unless the exhibit explicitly opens a larger transaction for a coherent multi-value operation. + +Examples of one transaction may include: + +- one host `set`; +- one UI slider change; +- one MIDI mutation; +- one universe change that also selects a default preset; +- one preset selection that updates several exposed values. + +All contract-visible state changes committed by one transaction share one resulting `stateRevision`. + +A native interaction originating on any presentation surface (Section 31) is +one mutation transaction, subject to this same rule, regardless of which +surface it originated on. + +### 14.2 Ordering + +The exhibit MUST: + +1. compute and commit the transaction; +2. increment `stateRevision` exactly once if externally visible state changed; +3. emit resulting `state.changed` / `selection.changed` events carrying that revision. + +### 14.3 No-op behavior + +A transaction that produces no externally visible state change MUST NOT increment `stateRevision`. + +### 14.4 Why both revision and sequence exist + +`stateRevision` tracks committed persistent-state history. + +Event `sequence` tracks delivery order of all emitted contract events, including impulses and errors. + +The counters serve different purposes and MUST NOT be treated as interchangeable. + +Multi-surface presentation does not introduce a second revision or sequence +counter of any kind. Every presentation surface of one exhibit instance +observes the same `stateRevision` history and the same event `sequence` +stream defined here. + +--- + +## 15. Source and Origin + +Normalized events MUST identify the authoritative source of the action when known. + +Base values: + +```text +ui +midi +hotkey +host +scenario +internal +system +``` + +A `source` property supplied by a host command MUST be ignored. + +The receiving bridge assigns the authoritative source. + +This distinction is required for recording and feedback-loop prevention. + +An interaction originating on a presentation surface other than the primary +surface (Section 31.3) uses the same `source` vocabulary as an interaction on +the primary surface — typically `ui`. This contract does not define a +per-surface source value; which surface an interaction originated on is not +contract-visible. + +--- + +## 16. Events + +A conforming XZBT 5.2 exhibit MUST publish normalized events for contract-visible operations. + +Base event types include: + +```text +state.changed +action.executed +selection.changed +capability.changed +registry.changed +error +``` + +Example: + +```json +{ + "xzbt": "5.3", + "type": "state.changed", + "sessionId": "sess-7f2a", + "sequence": 144, + "timestamp": 1799781200123, + "stateRevision": 29, + "target": "mix.master", + "value": 0.82, + "source": "midi" +} +``` + +Impulse example: + +```json +{ + "xzbt": "5.3", + "type": "action.executed", + "sessionId": "sess-7f2a", + "sequence": 145, + "timestamp": 1799781200234, + "target": "sfx.console-beep", + "args": {}, + "source": "ui" +} +``` + +### 16.1 Sequence + +Event `sequence` MUST increase monotonically within one session. + +It is session-scoped and resets when a new `sessionId` is issued. + +A host MUST reset its expected sequence after reconnect. + +### 16.2 Correlation + +When an event is caused directly by a host request, it SHOULD include the request's `requestId` as `correlationId`. + +### 16.3 Selection events + +A successful change to a Selection-kind target SHOULD emit `selection.changed`. + +### 16.4 Errors + +Request-scoped errors MUST be returned as responses. + +An exhibit MAY additionally publish an `error` event for observability. + +If published, it SHOULD carry the same source and `correlationId` as the rejected request. + +### 16.5 Recorder behavior + +NGN recording SHOULD normally record operator-originated events such as `ui`, `midi`, and `hotkey`. + +It SHOULD NOT automatically record its own `scenario` or host playback events back into the scenario being recorded unless explicitly configured. + +### 16.6 `registry.changed` and surfaces (Contract 5.3) + +`registry.changed` (Section 23) covers changes to `surfaces` in addition to +`targets` and capability metadata. A single `registryRevision` governs both; +this contract does not define a separate surface-registry revision. See +Section 31.6. + +--- + +## 17. Capability Model + +Capabilities are discoverable and stateful. + +Descriptor example: + +```json +{ + "id": "speech", + "state": "available" +} +``` + +Base states: + +```text +unsupported +available +loading +ready +busy +error +``` + +Definitions: + +- `unsupported`: the exhibit does not implement the capability. +- `available`: implemented, but not necessarily initialized. +- `loading`: initialization is in progress. +- `ready`: usable now. +- `busy`: currently occupied but still operational. +- `error`: initialization or operation failed. + +A capability may change state during a session. + +Capability changes MUST emit `capability.changed`. + +A host MUST treat the current discovered state as authoritative. + +A presentation surface (Section 31) MAY declare `requires` against +capability IDs defined here, using identical semantics to target `requires` +(Section 9). + +--- + +## 18. Speech + +An exhibit with speech support SHOULD expose: + +```text +speech.say +``` + +Example: + +```json +{ + "xzbt": "5.3", + "type": "invoke", + "requestId": "req-410", + "sessionId": "sess-7f2a", + "target": "speech.say", + "args": { + "text": "Engineering reports reactor output at eighty-seven percent." + } +} +``` + +The exhibit controls synthesis and playback. + +NGN supplies content. + +The contract does not require a specific TTS engine or a speech queue. + +If the exhibit has a usable fallback synthesis path while its preferred path is loading, it MAY accept `speech.say` through that fallback. + +A successful `speech.say` response means accepted for playback, not playback completed. + +--- + +## 19. Sound and Visual Events + +Exhibit-native sound and visual actions SHOULD be exposed as impulse targets where useful. + +Examples: + +```text +sfx.console-beep +sfx.warning-tone +event.alert-red +event.ship-arrival +``` + +State and impulse semantics MUST remain distinct. + +For example: + +```text +event.alert-red +``` + +may be an impulse, while: + +```text +alert.active +``` + +may represent current persistent alert state. + +NGN requests the action. + +The exhibit owns its implementation. + +--- + +## 20. Safe Text Injection + +An exhibit MAY expose safe text surfaces such as: + +```text +display.ticker +``` + +Text MUST be handled as data. + +The contract MUST NOT permit arbitrary: + +- HTML; +- JavaScript; +- CSS; +- selectors; +- executable expressions. + +Each text target SHOULD declare a maximum accepted length. + +This restriction applies equally to any text rendered by a presentation +surface (Section 31); surfaces introduce no new text-injection surface area. + +--- + +## 21. Telemetry + +Telemetry support is OPTIONAL. + +The contract defines how telemetry is represented if an exhibit exposes structured telemetry. It does not require every exhibit to implement telemetry. + +A telemetry target descriptor SHOULD declare: + +- identifier; +- label; +- value type; +- unit; +- minimum and maximum where applicable; +- writable status; +- update expectations if useful. + +Example: + +```json +{ + "id": "telemetry.engine.temperature", + "label": "Engine Temperature", + "kind": "range", + "valueType": "number", + "unit": "celsius", + "readable": true, + "writable": true, + "restorable": false +} +``` + +Existing SciFi-XZBT telemetry-like presentation is primarily audio activity and ticker text rather than a structured numeric telemetry model. + +Therefore structured writable telemetry in SciFi-XZBT is a new exhibit feature, not merely exposure of an existing data object. + +It may be implemented incrementally and is not required for the first contract-compatible milestone. + +--- + +## 22. Telemetry Ownership + +Writable telemetry MAY support source ownership. + +Recommended modes: + +```text +simulation +external +``` + +An external update MAY include a lease duration: + +```json +{ + "xzbt": "5.3", + "type": "set", + "requestId": "req-520", + "sessionId": "sess-7f2a", + "target": "telemetry.engine.temperature", + "value": 482.7, + "options": { + "ownership": "external", + "ttlMs": 5000 + } +} +``` + +After lease expiration, the exhibit MAY return to simulated ownership. + +The exact telemetry lease mechanism remains optional in 5.2, unchanged in 5.3. + +A simple explicit release mechanism is also acceptable. + +--- + +## 23. Registry Revision + +`registryRevision` represents the current set and metadata of discoverable targets. + +It SHOULD increment when targets or descriptor metadata actually change during a session. + +A `registry.changed` event MUST notify the host when the revision changes. + +The host then re-runs `describe`. + +Registry revision SHOULD NOT change merely because a fixed target becomes contextually unavailable. + +As of Contract 5.3, `registryRevision` also governs the `surfaces` array +(Section 31.6). One counter covers both; this contract does not define an +independent surface-registry revision. + +--- + +## 24. Error Codes + +Implementations SHOULD use stable machine-readable error codes. + +Recommended base codes include: + +```text +UNSUPPORTED_VERSION +INVALID_MESSAGE +INVALID_SESSION +UNKNOWN_TARGET +INVALID_VALUE +INVALID_ARGUMENTS +CAPABILITY_UNAVAILABLE +TARGET_READ_ONLY +TARGET_NOT_INVOKABLE +TARGET_NOT_SETTABLE +INTERNAL_ERROR +``` + +Human-readable error text is advisory. + +Hosts SHOULD branch on codes, not message strings. + +--- + +## 25. Security Boundary + +The contract MUST NOT provide arbitrary code execution. + +Target IDs MUST NOT be interpreted as property traversal. + +Contract payloads MUST NOT permit raw executable markup. + +The exhibit contract MUST NOT provide a primitive for fetching arbitrary URLs. + +External networking belongs to the host or to explicitly defined exhibit-native functionality. + +Both host and exhibit MUST validate incoming messages. + +Internet-facing authentication and authorization belong to XZBT-NGN, not the standalone exhibit. + +Same-origin `postMessage` transports MUST validate both `event.origin` and `event.source` against the expected host relationship. + +The same-origin requirement in this section extends to presentation surface +resolution and to any exhibit-internal attachment channel used between an +exhibit's own documents (Section 31.7). Neither introduces a new trust +boundary beyond the one already defined here. + +--- + +## 26. Scenario Independence + +The XZBT Exhibit Contract does not require a general-purpose scenario engine in the standalone exhibit. + +A scenario is an orchestration concept. + +XZBT-NGN may execute scenarios by issuing contract operations over time. + +A separately packaged exhibit may contain: + +- authored scenario data; +- a compact runtime; +- only the functionality necessary to execute that packaged scenario. + +The ordinary standalone exhibit does not need to load arbitrary scenario files. + +Scenario data MUST remain inert data and MUST NOT be interpreted as arbitrary executable code. + +--- + +## 27. Extensibility + +The base contract is exhibit-generic. + +A castle exhibit could expose: + +```text +weather.rain +fireplace.intensity +event.drawbridge +sfx.thunder +``` + +A train exhibit could expose: + +```text +engine.throttle +brake.pressure +event.whistle +telemetry.speed +``` + +NGN discovers these targets dynamically. + +--- + +## 28. Versioning + +### 28.1 Contract major + +Breaking protocol changes require a new contract major. + +### 28.2 Contract minor + +Backward-compatible additions may increase the minor version. + +### 28.3 Exhibit version + +Exhibit product version is independent from contract version. + +### 28.4 Registry revision + +Registry revision is runtime target metadata and does not imply a contract version change. + +### 28.5 State revision + +`stateRevision` is a runtime state-history counter and is not a protocol version. + +### 28.6 Scenario format + +Scenario format versioning is owned by XZBT-NGN and is outside the Exhibit Contract. + +### 28.7 Additive targets + +An exhibit may add targets without changing the contract version, provided existing target semantics remain compatible. + +### 28.8 Contract 5.3 (presentation surfaces) + +Contract 5.3 adds the optional `surfaces` field to `describe.result` +(Section 7) and the normative Presentation Surfaces model (Section 31), per +the rule in 28.2: this is a backward-compatible addition, not a breaking +change. Contract major remains 5. An exhibit reporting `{major: 5, minor: 2}` +is unaffected by this addition and remains fully conformant; a 5.3-aware host +MUST continue to interoperate with it exactly as under Contract 5.2 (Section +31.5). + +--- + +## 29. Conformance Minimum + +An exhibit is minimally XZBT Contract 5.2 compatible when it supports: + +- compatible handshake; +- normalized message envelope; +- `describe`; +- at least one discoverable target; +- `state.get` for readable persistent targets; +- `stateRevision`; +- normalized responses; +- normalized event emission; +- session event sequencing; +- command validation; +- version reporting. + +Speech, telemetry, MIDI, scenarios, Web3D, and any particular exhibit namespace are optional capabilities. + +This conformance minimum is unchanged by Contract 5.3. Presentation surfaces +(Section 31) are an optional capability of the same kind: an exhibit that +implements none of Section 31 and never emits `surfaces` remains fully +conformant, at either contract minor. + +--- + +## 30. Architectural Summary + +Version 5.2 keeps the Version 5 architecture intact while tightening implementation semantics. + +The key rule remains: + +**The exhibit exposes what it can do.** + +**The contract defines how that capability is described, observed, and invoked.** + +**XZBT-NGN decides how to orchestrate and integrate it.** + +Version 5.3 preserves this architecture without modification and adds exactly +one optional capability class — presentation surfaces (Section 31) — built +entirely on the existing session, state-revision, event, and registry-revision +mechanisms defined above. + +--- + +## 31. Presentation Surfaces (introduced in Contract 5.3) + +This section is new in Contract 5.3. It formalizes the multi-surface +presentation model approved in XZBT-NGN's Step 6.1 architecture document +(`docs/architecture/XZBT-Multi-Surface-Model-Step6.1.md`, Revision 2). Its +scope is narrowly the discovery and identification of presentation surfaces +and the contract-visible guarantees around them; it does not define display +transport, casting, or remote endpoints (explicitly out of scope — see +Section 31.9). + +### 31.1 Definition + +A **presentation surface** is a renderable, full-screen-capable visual view +of one logical exhibit instance, addressable by a stable identifier, that an +exhibit advertises as independently viewable. Every presentation surface of +one exhibit instance is driven by that exhibit's one authoritative +`stateRevision` history and one event `sequence` stream (Sections 13, 14, +16); this contract defines no mechanism by which two surfaces of the same +exhibit instance could observe divergent state. + +A presentation surface is not a target (Section 9), not a capability +(Section 17), and not the host's own control/administration interface. It +MUST NOT be represented using any target `kind`. + +### 31.2 Surface descriptor + +When an exhibit advertises presentation surfaces, each entry in the +`surfaces` array (Section 7) MUST be an object with the following fields: + +| Field | Requirement | Notes | +| --- | --- | --- | +| `id` | REQUIRED string | MUST conform to the canonical target-ID grammar (Section 8.1). No separate identifier grammar is defined for surfaces. | +| `label` | REQUIRED string | Human-readable. | +| `kind` | REQUIRED, constant `"surface"` | Identifies the descriptor type. | +| `primary` | REQUIRED boolean | Governed by Section 31.3. | +| `url` | REQUIRED string | Governed by Section 31.4. | + +The following fields are OPTIONAL: + +| Field | Purpose | +| --- | --- | +| `description` | Longer human-readable text. | +| `role` | Advisory grouping hint (e.g. `"control"`, `"ambient"`, `"information"`). No contract behavior depends on this field's value in Contract 5.3. | +| `aspectRatio` | Advisory sizing hint (e.g. `"16:9"`). | +| `category` | Mirrors target `category` (Section 9) for consistent grouping. | +| `requires` | Array of capability IDs (Section 17), with identical semantics to target `requires`: the surface remains discoverable but SHOULD be presented as degraded or unavailable when a required capability is not `ready`. | + +No field of a surface descriptor MAY reference a display transport, casting +protocol, network endpoint, or device class (Section 31.9). + +### 31.3 The `primary` invariant and validation order + +`surfaces` (Section 7) has exactly three conformant top-level forms: + +1. **Absent.** The exhibit does not advertise multi-surface presentation. + Functionally equivalent to a Contract 5.2 `describe.result`. +2. **Present and empty (`[]`).** MUST be treated identically to form 1 by a + conformant host. +3. **Present and non-empty.** Evaluated using the deterministic validation + order below. + +A host MUST evaluate a non-empty `surfaces` array in the following order: + +1. Validate each individual surface descriptor against Section 31.2 and + Section 31.4. +2. Discard individually invalid entries (Section 31.5); retain only the + valid entries as the working set. +3. Evaluate the `primary` invariant against the working set, not against the + original array: + - If the working set is empty (every entry was individually invalid), + treat `surfaces` as if it were absent (form 1). + - If the working set contains exactly one entry with `primary: true`, + that entry is the primary surface and the working set is the + conformant surface catalog for this `describe.result`. + - If the working set contains zero entries with `primary: true`, or more + than one, the surface catalog as a whole is malformed: the host MUST + reject the entire working set (not just the offending entries), fall + back to treating `surfaces` as absent (form 1), and SHOULD report a + diagnostic. + +This order is intentional and normative: the `primary` invariant is always +evaluated after individually-invalid entries have already been discarded, so +that one malformed entry cannot be mistaken for a missing or duplicate +`primary` among otherwise-valid entries, and so that discarding a malformed +entry can deterministically change whether the invariant holds. + +The primary surface, once determined by this process, is the surface a +Section-31-aware host that provides no operator surface selection opens by +default, and is the surface whose standalone-use guarantee is unconditional +(Section 31.8). + +### 31.4 URL resolution + +`url` MUST be one of: + +- a path relative to the exhibit's own base document location — the same + base URL already used to establish the exhibit's session; +- such a relative path with an appended query string and/or fragment; +- a bare query string and/or fragment, resolving against the exhibit's own + base document, for a single-page exhibit whose surfaces are views within + one already-served document. + +A host resolving `url` MUST: + +1. resolve it against the exhibit's already-established base URL, not + against the host's own administrative interface location; +2. reject the entry (Section 31.5) if the resolved URL is not same-origin + with that base, per the same-origin requirement in Section 25; +3. apply the same path-containment validation the host already applies to + the exhibit's primary document, if any such validation exists in that + host implementation. + +Absolute, protocol-relative, or cross-origin `url` values MUST be rejected as +malformed individual entries; they do not invalidate the rest of the array. + +### 31.5 Individual entry validation + +A host MUST validate each surface entry independently, as step 1 of the +order defined in Section 31.3. An entry missing a required field, using an +invalid `id` (Section 8.1), or specifying a `url` that fails Section 31.4 is +individually invalid; the host SHOULD skip only that entry, log a +diagnostic, and continue evaluating the remainder of `surfaces`. Individual +entry validation never by itself invalidates the whole array — only the +structural `primary` evaluation performed afterward, against the surviving +valid entries (Section 31.3, steps 2–3), can do that. + +A host that does not implement any Section 31 behavior MAY safely ignore the +`surfaces` field entirely; doing so is fully conformant with the +`describe.result` schema, since the field is OPTIONAL (Section 7). + +An exhibit implementing only Contract 5.2 behavior is unaffected: it never +emits `surfaces`, and no 5.3-only requirement applies to it. + +### 31.6 Registry governance + +`surfaces` is governed by `registryRevision` (Section 23) exactly as +`targets` is. There is no independent surface-registry counter. A +`registry.changed` event (Section 16.6) requires the host to re-run +`describe` and re-read both `targets` and `surfaces`. + +### 31.7 State and interaction + +All presentation surfaces of one exhibit instance share that instance's one +session, one `stateRevision` history, and one event `sequence` stream +(Sections 13, 14, 16); this contract defines no per-surface state channel +and no per-surface session. How an exhibit internally propagates state to +each surface's rendering code is an exhibit implementation detail outside +this contract's normative scope. + +The normative requirement on interaction is narrower than "every native +interaction becomes a public target": any surface-originated interaction +that changes contract-visible persistent state or executes a contract-visible +action MUST obey the same mutation, revision, source, and event semantics +(Sections 14, 15, 16) as the equivalent operation originating from the +exhibit's primary UI or through a contract `set`/`invoke` (Section 14.1). A +surface-local interaction that does not change contract-visible state and +does not execute a contract-visible action — purely presentational +interaction confined to that surface — remains exhibit-internal and is +outside this contract's scope; this contract does not require it to have a +corresponding target. + +An exhibit's internal mechanism for connecting a surface's rendering code to +its own state, including any same-origin channel used between the exhibit's +own documents, is not part of the message envelope defined in Section 6 and +is never observed by the host. + +### 31.8 Standalone behavior + +Per the governing rule in Section 2, an exhibit's `primary` surface (Section +31.3) — or, when `surfaces` is absent or empty, the exhibit's ordinary entry +point — MUST remain fully and unconditionally usable standalone, with no +dependency on XZBT-NGN, on a contract session, or on any other surface. + +A non-primary surface SHOULD remain directly usable without XZBT-NGN. This +contract does not require every non-primary surface to be usable in complete +isolation from the exhibit's other documents; an exhibit MAY have a +non-primary surface depend on another of its own documents being present, as +an exhibit-internal implementation consequence of Section 31.7, provided that +dependency is never on XZBT-NGN itself. + +### 31.9 Non-goals + +This section defines discovery and identification of presentation surfaces +only. It does not define, and MUST NOT be extended by implementations to +imply: + +- casting or remote display protocols; +- network display endpoints or device classes; +- an assignment mechanism between a surface and a physical or logical + display; +- any change to session, state, revision, or event semantics beyond the + cross-references added in Sections 7, 9.4, 13, 14.1, 14.4, 15, 16.6, 17, + 20, 23, 25, and 29 of this document. + +Those concerns are reserved for future contract or XZBT-NGN work and are +explicitly out of scope for Contract 5.3. + +--- + +## 32. Version 5.3 Summary + +Contract 5.3 is Contract 5.2 plus one optional, additive capability: +presentation surfaces (Section 31). No existing normative requirement is +weakened, removed, or reinterpreted. An exhibit or host that implements +nothing in Section 31 is unaffected and remains conformant. The architectural +rule from Section 30 is unchanged: the exhibit exposes what it can do, the +contract defines how that is described and invoked, and XZBT-NGN decides how +to orchestrate it — now including, optionally, orchestrating which of an +exhibit's several presentation surfaces is currently shown. + +--- + +## 39. Changelog (5.2 → 5.3) + +This section exists only in 5.3 and has no 5.2 counterpart. + +**Added:** + +- `surfaces` OPTIONAL field on `describe.result` (Section 7). +- Section 31, Presentation Surfaces: definition, descriptor schema, the + `primary` invariant and its three conformant forms, URL resolution, + validation/backward-compatibility rules, registry governance, the + state/interaction guarantee, standalone-use requirements, and explicit + non-goals. +- Section 28.8, recording the versioning rationale for this addition. +- Cross-reference sentences in Sections 5.2 (hello response commentary), + 9.4, 13, 14.1, 14.4, 15, 16.6, 17, 20, 23, 25, 29, and 30, each noting how + the existing normative rule in that section extends to, or is unaffected + by, presentation surfaces. None of these cross-references change the + normative requirement already stated in 5.2 for that section. + +**Changed:** + +- Header metadata (document version, compatibility statement) — the + supersedes wording was tightened during the pre-6.2 cleanup pass to state + plainly that no existing 5.2 requirement was removed, weakened, or + reinterpreted, rather than characterizing which sections happened to + receive cross-references. +- Illustrative `"xzbt"` and `contract.minor` values in JSON examples updated + from `"5.2"` / `2` to `"5.3"` / `3` throughout, for internal consistency + within this document. This is cosmetic within the example payloads and + does not alter any example's normative meaning. +- Section 31.3 was restructured, during the same cleanup pass, into an + explicit, deterministic validation order (validate individual entries → + discard invalid ones → evaluate the `primary` invariant against the + surviving set) so that a discarded individually-invalid entry cannot leave + the `primary` determination ambiguous. Section 31.5 was narrowed to cover + only individual-entry validation and now cross-references 31.3 for the + structural `primary` evaluation, rather than restating it. This is a + clarification of Revision 2's intent, not a new rule. +- Section 31.7's interaction requirement was narrowed from "every successful + surface interaction behaves like `set`/`invoke`" to the intended scope: + this requirement applies only to interactions that change contract-visible + state or execute a contract-visible action. Purely surface-local, + non-contract-visible interaction is explicitly out of scope and is not + required to have a corresponding target. +- Section 31.3's reference to "a host with no surface-specific UI" was + reworded to "a Section-31-aware host that provides no operator surface + selection," to avoid it being read as covering a host that ignores Section + 31 entirely. + +**Removed:** nothing. No 5.2 requirement is weakened, deleted, or +reinterpreted by this document. + +**Not changed:** Sections 1–4, 6, 8.2–8.3, 10–12, 18–19, 21–22, 24, 26–27, +28.1–28.7 carry no 5.3-specific content and are reproduced from 5.2 +unmodified except for the cosmetic example-version updates noted above. diff --git a/docs/reference/Museum-Gallery-Step6.2-Verification.md b/docs/reference/Museum-Gallery-Step6.2-Verification.md new file mode 100644 index 0000000..3f464cc --- /dev/null +++ b/docs/reference/Museum-Gallery-Step6.2-Verification.md @@ -0,0 +1,395 @@ +# Museum Gallery — Step 6.2 Verification + +**Status: STEP 6.2 COMPLETE — REFERENCE MULTI-SURFACE MODEL PROVEN.** The +real-browser harness (Section K) has now been executed and passed 13/13 +required proof points (16/16 including 3 additional harness self-checks), +repeated across 6 consecutive runs with zero flakes. + +## A. Purpose + +This document records the verification evidence for Phase 6.2 of Step 6: +the Museum Gallery multi-surface reference exhibit, built against Contract +5.3 (`docs/contract/XZBT-Exhibit-Contract-Specification-v5.3.md`) and the +Step 6.1 architecture (`docs/architecture/XZBT-Multi-Surface-Model-Step6.1.md`, +Revision 2). + +## B. Architecture summary + +One Museum Gallery exhibit instance exposes three presentation surfaces — +`surface.control` (primary), `surface.artifact`, `surface.info-wall` — all +views onto one Exhibit State Core, constructed exactly once, only by the +primary surface's document (`control.html`). Non-primary surfaces +(`artifact.html`, `info-wall.html`) never load the Core-constructing files +(`contract-core.js`, `exhibit.js`, `contract-adapter.js`) at all; they load +only the exhibit-internal attachment bus (`surface-bus.js`) and attach to the +Core that `control.html` owns. This is structural, not just documented +convention: those two documents are incapable of constructing a second Core +because the constructor (`window.MuseumGalleryContract.create`) is simply +undefined in their execution context — proven directly in +`tests/museum-gallery.test.js` ("Exactly one Exhibit State Core exists"). + +## C. Chosen internal attachment transport + +Same-origin `BroadcastChannel`, channel name `xzbt-museum-gallery-core-v1`, +with `control.html` as the sole identified owner. Full rationale and message +shapes are documented in +`test-fixtures/reference-exhibits/museum-gallery/README.md`. This transport +is exhibit-internal per Contract 5.3 §31.7/§31.9 — it is not part of the +XZBT wire protocol, carries no contract envelope, and Contract 5.3 was not +modified to encode it. + +## D. Surface list + +| id | label | primary | url | +| --- | --- | --- | --- | +| `surface.control` | Control Room | `true` | `control.html` | +| `surface.artifact` | Artifact Display | `false` | `artifact.html` | +| `surface.info-wall` | Information Wall | `false` | `info-wall.html` | + +All three are discovered from a live `core.describe().surfaces` call in +`tests/museum-gallery.test.js`'s first test, which also asserts the +canonical dotted-ID grammar, the single-`primary` invariant, and +relative-only `url` values. + +## E. Shared state + +`artifact.selected` (selection: `the-orrery` / `star-map` / `meteorite`), +`lighting.level` (range 0–1), `rotation.speed` (range 0–2), +`labels.enabled` (boolean state), plus one impulse, `action.spotlight-flash`, +used to prove `action.executed` propagation across surfaces. + +## F. Proof of one authoritative Core + +Three lines of evidence: + +1. **Structural**: non-primary surface test contexts never load the files + that could construct a Core (`tests/museum-gallery.test.js`, "Exactly one + Exhibit State Core exists" and the artifact/info-wall boot scripts + themselves — see their file headers in + `test-fixtures/reference-exhibits/museum-gallery/`). +2. **Behavioral**: every synchronization test drives state changes from + both the "primary" (direct `core.applyMutation`/`invokeAction` calls, + standing in for the Control Room's own controls) and from a non-primary + surface's `link.mutate(...)` call, and asserts the *other* surface(s) + converge on the identical value with the identical `stateRevision` — + there is no code path in this exhibit that could produce disagreement, + because there is only one place state is ever written. +3. **Sequence**: `core.eventLog()` sequence numbers are asserted strictly + monotonic and non-duplicated across mutations that originated from + different "surfaces" in the same test run — one stream, not one per + surface. + +## G. Reference exhibit implementation + +Files added under `test-fixtures/reference-exhibits/museum-gallery/`: +`exhibit.js` (domain model), `contract-adapter.js` (Contract 5.3 wiring, +including the `SurfaceCatalog`), `surface-bus.js` (attachment transport), +`control.html`/`control.boot.js` (primary), `artifact.html`/ +`artifact.boot.js` and `info-wall.html`/`info-wall.boot.js` (non-primary), +`style.css`, `index.html` (redirect to `control.html`), `README.md`, +`verification.html` (browser harness — see Section K). + +## H. NGN discovery implementation + +**Not built in Phase 6.2, by design.** The Step 6.2 brief explicitly +excludes NGN generic surface discovery, a surface picker, and open/close +window controls from this phase (those are Phase 6.3). Nothing in +`src/` was touched. + +## I. Local rendering implementation + +Also explicitly out of scope for 6.2 (Phase 6.4). `verification.html` opens +the three surfaces as iframes purely as a **test harness** for this +document's own evidence gathering, not as NGN product behavior. + +## J. Shared-state synchronization — automated test results + +`tests/museum-gallery.test.js`, run via `node --test tests/museum-gallery.test.js`: + +``` +1..16 +# tests 16 +# pass 16 +# fail 0 +``` + +Full suite together with the pre-existing tests (`node --test 'tests/*.test.js'`): + +``` +1..18 +# tests 18 +# pass 18 +# fail 0 +``` + +The 16 Museum Gallery tests cover proof points 1–13 from the Step 6.2 brief +directly: + +1. Contract 5.3 `describe` includes `surfaces` — covered. +2. exactly one primary surface — covered. +3. surface IDs validate — covered (both the describe-level test and four + dedicated `SurfaceCatalog` unit tests covering the Part A2 validation + order: an individually-invalid entry discarded before the primary + invariant is evaluated, zero-primary rejection, multiple-primary + rejection, and all-entries-discarded-is-absent-not-malformed). +4. surface URLs validate — covered, including a dedicated test for + absolute/protocol-relative URLs being discarded. +5. one authoritative Core — covered (Section F). +6. primary → non-primary synchronization — covered. +7. non-primary → primary and non-primary → other non-primary — covered in + one test, asserting both directions from a single surface-originated + mutation. +8. `stateRevision` increments correctly — covered, including the "increments + exactly once" assertion on the surface-originated mutation test. +9. one event-sequence stream — covered. +10. close/detach does not mutate state — covered (asserts `stateRevision` + and `eventLog().length` are unchanged immediately after detach). +11. reopen/reattach gets current state — covered, both as its own test and + as part of the detach test (attach → mutate → detach → mutate again → + reattach → assert the reattached surface sees the latest value, not a + stale one). +12. no independent per-surface state — covered directly (two surfaces + attached simultaneously are asserted to receive byte-identical event + sequences) and structurally (Section F). +13. primary still works standalone — covered: a dedicated test drives the + Core through `stateSnapshot`/`applyMutation`/`invokeAction` with zero + surface-bus or NGN involvement at all. + +**What this test suite genuinely proves, precisely stated**: these tests use +Node's own global `BroadcastChannel` implementation (the same message-passing +spec surface the DOM implementation exposes, keyed by channel name, +process-wide) across independent `vm` contexts that do not share JavaScript +realm state — this is a real, if not browser-hosted, proof of the +cross-document attachment sequence, not a mock of it. What it does **not** +prove is browser-specific behavior: real separate windows/tabs, real +same-origin-over-HTTP enforcement, real page-unload `detach()` firing, or +visual rendering. That gap is Section K/L below. + +## K. Reference exhibit browser verification + +**Executed this session — real browser, real HTTP origin, real +cross-document `BroadcastChannel`. Result: 13/13 required proof points +passing (16/16 including 3 additional harness self-checks), on every one of +6 consecutive runs.** + +`test-fixtures/reference-exhibits/museum-gallery/verification.html` opens +all three surfaces (`control.html`, `artifact.html`, `info-wall.html`) as +three separate same-origin iframe documents and drives the 13 proof points +listed in Section J directly against real DOM state and a real, +non-mocked `BroadcastChannel`, logging PASS/FAIL per check plus a +pass/total summary. + +**Why this session, not the user's machine.** The device bridge's file +mount (`device_bash`) has remained unreachable all session ("A Windows +update released September 8 prevents Claude's workspace from reaching your +files"), so `npm start` could not be run on the user's machine as +originally planned. Rather than leave the browser-verification requirement +unsatisfied, the exhibit's already-staged files (present in this session's +own working copy from building Phase 6.2) were served over a real HTTP +origin (`http://127.0.0.1:4173/...`) by a throwaway static file server +started in this session's cloud container, and driven with a genuine +Chromium instance (Playwright, headless) — not a headless DOM shim, not +`file://`, and not Node's `vm`-context proof from Section J. This satisfies +the same same-origin requirement `npm start` would have: a real HTTP +origin, not an opaque per-navigation `file://` origin. The static server +and the Playwright driver script are test infrastructure only; neither is +part of the Museum Gallery deliverable and neither was added to the repo. + +**Harness hardening applied this pass.** The harness previously used fixed +sleeps (`await wait(300)`, `wait(200)`, `wait(250)`, …) standing in for +attachment and propagation delays. Every one of those was replaced with +condition-based polling against the actual signal being waited on: +`isAttached()` on both non-primary surface links before proceeding, DOM +content matching the expected post-mutation value (for the primary→non-primary +and non-primary→primary/non-primary checks), and `isAttached()` again after +the Information Wall's reopen. A failed poll now dumps diagnostics (the +Core's current value for the target, the surface's DOM value, the current +`stateRevision`, and the 5 most recent events) instead of a bare FAIL line. +Only `verification.html` (the test harness) was changed — no change was +made to `surface-bus.js`, `contract-core.js`, or any Museum Gallery boot +script, since no run of the hardened harness ever demonstrated a defect in +them (see below). + +**One flaky result during hardening, and what it showed.** The very first +run against the newly-hardened harness (before any polling logic existed) +returned 12/16, with `artifactLink.isAttached()` still `false` at the +3-second poll deadline and two downstream checks failing in consequence; +the diagnostic dump showed the Core's own state (`rotation.speed: 1.7`, +`stateRevision: 1`) was already correct at that moment — only the +`artifact.html` iframe's attachment hadn't yet been observed by the +poll. Every subsequent run (6 consecutive, described above) passed 16/16 +with the identical unmodified exhibit code, which is inconsistent with a +code defect and consistent with a one-time cold-start artifact (first +Chromium launch, first page load, first `BroadcastChannel` construction in +that process). No exhibit or surface-bus code was changed in response, +per the instruction to leave the state architecture and surface bus alone +absent a deterministic proof of a defect — six identical, clean runs is +that determinism check, and it did not implicate the exhibit. + +**One benign console error observed, not a defect.** On every run, the +Information Wall's reopen step (proof point 11: the iframe's `src` is set +back to `info-wall.html`, forcing a real navigation) produces a harmless +`pageerror` from the *previous* document's `beforeunload` handler: +`info-wall.boot.js` calls `link.detach()`, which calls +`channel.postMessage(...)`, but the browser has already begun invalidating +that document's `BroadcastChannel` by the time `beforeunload` runs, so the +`postMessage` throws `InvalidStateError: Channel is closed`. This is a +teardown-time exception in code that no longer affects any observable +state (the document is already being discarded) — it does not fail any of +the 13 proof points in any of the 6 runs, and `surface-bus.js`'s own +comment already documents that detach is bookkeeping-only ("no state is +held by the surface, so nothing reconciles"). It is recorded here as an +observed cosmetic rough edge, not fixed, because it does not meet the bar +of a proven implementation defect and touching `surface-bus.js` is outside +this pass's narrow scope. + +See Section J for exactly what the Node-level `vm`-context tests prove on +their own, and Section R for the residual limitation this run does still +leave open (real separate top-level windows vs. iframes). + +## L. SciFi-XZBT Observation integration + +Not applicable to Phase 6.2 — that is Phase 6.7/6.8. + +## M. SciFi-XZBT interoperability results + +Not applicable to Phase 6.2. + +## N. Automated tests + +See Section J. `node --test 'tests/*.test.js'`: 18/18 passing, including the +2 pre-existing Haunted House tests, confirmed unaffected by the shared +`contract-core.js` changes (see Section P). + +## O. Browser/runtime verification + +See Section K. 13/13 required proof points passing in a real browser over a +real HTTP origin, 6/6 consecutive clean runs. + +## P. Genericity verification + +- `test-fixtures/reference-exhibits/shared/contract-core.js` was extended + additively: every new field (`options.surfaces`, `options.contractMinor`, + `options.xzbtVersion`) defaults to the exact pre-5.3 behavior when + omitted. `describe()` omits the `surfaces` key entirely unless a + `SurfaceCatalog` was supplied — verified directly by the "Non-surface-aware + exhibits are unaffected" test. +- Aquarium, Planetarium, and Haunted House were **not modified**. Haunted + House's existing test suite (`tests/haunted-house.test.js`) was rerun + unchanged against the modified shared core and still passes 2/2. +- Nothing under `src/` (the NGN host itself) was touched in Phase 6.2. +- Museum Gallery's own files contain no SciFi-XZBT vocabulary, and nothing + in `contract-core.js`'s new `SurfaceCatalog` logic references any + exhibit's domain content — it validates only the generic Contract 5.3 + §31.2/§31.3 shape (id grammar, required fields, the primary invariant, URL + relative-safety). + +## Q. Files changed + +New: + +``` +test-fixtures/reference-exhibits/museum-gallery/exhibit.js +test-fixtures/reference-exhibits/museum-gallery/contract-adapter.js +test-fixtures/reference-exhibits/museum-gallery/surface-bus.js +test-fixtures/reference-exhibits/museum-gallery/control.html +test-fixtures/reference-exhibits/museum-gallery/control.boot.js +test-fixtures/reference-exhibits/museum-gallery/artifact.html +test-fixtures/reference-exhibits/museum-gallery/artifact.boot.js +test-fixtures/reference-exhibits/museum-gallery/info-wall.html +test-fixtures/reference-exhibits/museum-gallery/info-wall.boot.js +test-fixtures/reference-exhibits/museum-gallery/index.html +test-fixtures/reference-exhibits/museum-gallery/style.css +test-fixtures/reference-exhibits/museum-gallery/README.md +test-fixtures/reference-exhibits/museum-gallery/verification.html +tests/museum-gallery.test.js +docs/reference/Museum-Gallery-Step6.2-Verification.md (this file) +``` + +Modified (additive only — see Section P): + +``` +test-fixtures/reference-exhibits/shared/contract-core.js +``` + +Not modified: `src/`, `public/`, `server/`, Aquarium, Planetarium, Haunted +House, Contract 5.2, Contract 5.3, the Step 6.1 architecture document. + +## R. Known limitations + +1. Live browser verification (`verification.html`) has been executed and + passed 13/13 (Section K), but only as three iframes under one top-level + page, not as three genuinely separate browser windows/tabs opened + independently on the user's machine via `npm start` — see item 4 below + for what that gap does and doesn't matter for. +2. NGN attachment interoperability is not exercised: Museum Gallery declares + `xzbt: '5.3'`; today's pre-6.3 NGN host always sends `xzbt: '5.2'` on + every envelope, so an actual attach attempt from the current NGN would + currently be rejected at the envelope-version check in + `ContractCore.handleRequest`, never reaching contract-major negotiation. + This is expected — NGN's own Contract 5.3 awareness is Phase 6.3+ work — + but is recorded here explicitly rather than left implicit. +3. The `role` field on surface descriptors (`control` / `ambient` / + `information`) is illustrative only, per Contract 5.3 §31.2; no host + behavior depends on its value yet. +4. `verification.html`'s iframe-based harness proves same-origin, + same-tab-group multi-document behavior (now executed, Section K); it + does not by itself prove behavior across genuinely separate top-level + browser windows (e.g. window-close/`beforeunload` timing can differ + slightly between an iframe removal and a real window close — Section K + already surfaced one such teardown-time quirk, observed as benign). A + follow-up pass opening the three surfaces as real separate windows on + the user's own machine via `npm start` is recommended before Phase + 6.6's broader reference-exhibit closure, though Phase 6.2 itself only + requires proving the architecture, which the iframe harness and the + Node-level tests both now do. + +## S. Deferred Step 7 concerns + +Casting, remote display endpoints, Chromecast/Google TV, network display +discovery — untouched, as required. Nothing in Museum Gallery's surface +descriptors or attachment transport names a display endpoint of any kind. + +## T. Architecture review findings + +- No hidden duplicate state machine: confirmed structurally (Section F) — + non-primary documents cannot construct a Core even accidentally, since the + constructing function is undefined in their scope. +- No host/exhibit ownership confusion: NGN was not touched; every line of + new code lives in the exhibit or its tests. +- No implicit hard-coded surfaces: `surfaces` are discovered from + `core.describe()`'s live `SurfaceCatalog` output in every test; nothing + hard-codes surface names outside the exhibit's own descriptor list. +- No transport coupling: Contract 5.3 was not touched by this phase, and the + BroadcastChannel choice lives entirely in `surface-bus.js` and this + document/README, never in the contract. +- No display-endpoint assumptions: confirmed (Section S). +- No revision inconsistencies: a dedicated test asserts `stateRevision` + increments exactly once per transaction regardless of originating surface, + and `registryRevision` is read directly from `core.registryRevision` with + no separate surface-registry counter anywhere in the implementation. +- Reconnect/stale-window behavior: not applicable in Phase 6.2 (no NGN + session, no window lifecycle beyond the exhibit's own attach/detach) — + deferred to Phase 6.5/6.6 as the Step 6.1 document already anticipated. + +## U. Final verdict + +**STEP 6.2 COMPLETE — REFERENCE MULTI-SURFACE MODEL PROVEN** + +The multi-surface architecture is implemented and proven by two +independent lines of evidence: 16 passing Node-level tests exercising a +real cross-context `BroadcastChannel` attachment sequence (Section J), and +a real-browser run of `verification.html` — three separate same-origin +documents served over a genuine HTTP origin and driven with headless +Chromium — passing all 13 required proof points (16/16 including harness +self-checks) across 6 consecutive runs with no flakes and no code changes +to the exhibit or its attachment transport (Section K). 2 pre-existing +tests confirm no regression to the other reference exhibits. The residual +item (Section R.4/R.1) — real separate top-level windows on the user's own +machine via `npm start`, rather than same-page iframes — is a +lower-confidence gap the brief's 13 proof points do not require closed for +Phase 6.2, and is recommended before Phase 6.6 rather than blocking this +verdict. + +Per the Step 6 roadmap: **NGN Phase 6.3 (generic surface discovery in the +host) has not begun.** No file under `src/` was touched. diff --git a/test-fixtures/reference-exhibits/museum-gallery/README.md b/test-fixtures/reference-exhibits/museum-gallery/README.md new file mode 100644 index 0000000..8fa8610 --- /dev/null +++ b/test-fixtures/reference-exhibits/museum-gallery/README.md @@ -0,0 +1,121 @@ +# Museum Gallery — Step 6.2 multi-surface reference exhibit + +A deliberately small XZBT Contract 5.3 exhibit built to prove the +multi-surface presentation model from +`docs/architecture/XZBT-Multi-Surface-Model-Step6.1.md` (Revision 2) and +`docs/contract/XZBT-Exhibit-Contract-Specification-v5.3.md` Section 31 — not +to be visually elaborate. See +`docs/reference/Museum-Gallery-Step6.2-Verification.md` for the full +verification writeup; this file documents the exhibit's own structure and +its chosen internal attachment transport. + +## Entry point + +`control.html` is the primary surface (`surface.control`, `primary: true`) +and the exhibit's canonical entry point — open it directly for full +standalone operation, exactly like any other reference exhibit's +`index.html`. `index.html` in this directory is a plain redirect to +`control.html`, kept only for directory-listing consistency; it has no +behavior of its own. + +## Surfaces + +| id | label | primary | url | role | +| --- | --- | --- | --- | --- | +| `surface.control` | Control Room | `true` | `control.html` | control | +| `surface.artifact` | Artifact Display | `false` | `artifact.html` | ambient | +| `surface.info-wall` | Information Wall | `false` | `info-wall.html` | information | + +## Exactly one Exhibit State Core + +`control.html` is the only document that loads `contract-adapter.js` (and +therefore the only document that constructs an `XZBTContractCore.ContractCore` +instance). `artifact.html` and `info-wall.html` load only `surface-bus.js` — +they are structurally incapable of constructing a second Core; the shared +`tests/museum-gallery.test.js` suite asserts this directly (see "Exactly one +Exhibit State Core exists" in that file). + +## Chosen internal attachment transport: `BroadcastChannel` + +`surface-bus.js` implements the generic attachment sequence from Step 6.1 +§7.2 using a same-origin `BroadcastChannel` named `xzbt-museum-gallery-core-v1`, +with `control.html` as the single, clearly identified authoritative owner +document (Contract 5.3 §31.7's "cross-document attachment" shape). + +Why `BroadcastChannel` over the other two permitted shapes: + +- it needs no window-handle bookkeeping (unlike `postMessage` to a specific + `window` reference, which breaks if the owner window reference is lost or + the surface was opened independently rather than via `window.open`); +- it needs no separate worker lifecycle (unlike `SharedWorker`, which is + unsupported in some embedding contexts and adds a process to reason + about for a reference fixture this small); +- every participant — owner or surface — only ever needs to know one + string (the channel name), which keeps `control.html`, `artifact.html`, + and `info-wall.html` fully decoupled from each other; none of them + reference the other documents by name or handle. + +This transport is exhibit-internal. It carries no XZBT contract envelope, is +never observed by a host, and is not mentioned anywhere in Contract 5.3 — +per Step 6.1 §7.2 and Contract 5.3 §31.7/§31.9, the contract only needs to +know that surfaces exist and how a host opens one. A different exhibit is +free to choose `SharedWorker`, in-process attachment, or another same-origin +mechanism entirely. + +### Message shapes (informal, exhibit-internal only) + +- surface → owner: `{ type: 'attach', requestId }` +- owner → surface: `{ type: 'attach.snapshot', inReplyTo, snapshot, registryRevision }` +- owner → all: `{ type: 'core-event', event }` — one relayed copy of every + normalized event the Core already emits (`state.changed`, + `selection.changed`, `action.executed`, …) +- surface → owner: `{ type: 'mutate', kind: 'set'|'invoke', target, value|args }` + — routed straight into `core.applyMutation` / `core.invokeAction` with + `source: 'ui'`, the same chokepoint the primary UI's own controls use +- surface → owner: `{ type: 'detach' }` — bookkeeping only; never mutates + state + +## Shared state + +`artifact.selected` (selection), `lighting.level` (range 0–1), +`rotation.speed` (range 0–2), `labels.enabled` (state/boolean), plus one +impulse, `action.spotlight-flash`, to prove `action.executed` propagation +across surfaces. + +## Native interactions proving the canonical mutation path + +- Artifact Display's "Cycle artifact" button calls `link.mutate('set', + 'artifact.selected', …)`. +- Information Wall's "Toggle labels" button calls `link.mutate('set', + 'labels.enabled', …)`. + +Both are relayed by the bus into `core.applyMutation` on the one Core that +`control.html` owns — the same call the Control Room's own controls and a +contract `set` from NGN would make. See the verification document for the +event-sequence and stateRevision proof. + +## Running it directly (no test harness) + +Serve this repository with `npm start` and open: + +``` +http://127.0.0.1:4173/test-fixtures/reference-exhibits/museum-gallery/control.html +``` + +Then open `artifact.html` and `info-wall.html` in separate tabs/windows from +the same origin. Changing state in any one window updates the other two. +Closing and reopening a non-primary surface reflects current state +immediately. Opening `artifact.html` or `info-wall.html` alone, with +`control.html` not open anywhere, shows a "Waiting for the Control Room +surface to be open…" state rather than inventing its own state. + +## Known limitation: NGN attachment is not exercised in Step 6.2 + +`control.html` includes the optional `XZBTHostTransport`, matching every +other reference exhibit. Because Museum Gallery declares `xzbtVersion: +'5.3'` while the current (pre-6.3) NGN host always sends the envelope tag +`xzbt: '5.2'`, an actual attach attempt from today's NGN would be rejected +at the envelope-version check rather than negotiating a Contract-major-5 +session. This is expected and intentional: NGN's generic surface discovery +and version negotiation are Phase 6.3+ work, explicitly out of scope for +Step 6.2. See the verification document's known-limitations section. diff --git a/test-fixtures/reference-exhibits/museum-gallery/artifact.boot.js b/test-fixtures/reference-exhibits/museum-gallery/artifact.boot.js new file mode 100644 index 0000000..8d61ffe --- /dev/null +++ b/test-fixtures/reference-exhibits/museum-gallery/artifact.boot.js @@ -0,0 +1,76 @@ +/* + * Museum Gallery — Artifact Display boot (non-primary surface). + * + * This document deliberately never loads contract-core.js, exhibit.js, or + * contract-adapter.js. It has no way to construct an Exhibit State Core -- + * it can only attach to the one the Control Room document created, over + * surface-bus.js. If the Control Room is not open, this degrades to a clear + * waiting state rather than inventing its own state (Contract 5.3 Section + * 31.8, Step 6.1 Section 7.2). + */ +(function () { + 'use strict'; + + var Shell = window.XZBTShell; + var el = Shell.el; + var statusEl = el('status'); + var announcer = new Shell.Announcer({ node: el('announcement'), idleText: 'Waiting for shared state.' }); + var cycleButton = el('cycle-button'); + + var ARTIFACT_ORDER = ['the-orrery', 'star-map', 'meteorite']; + var ARTIFACT_LABELS = { 'the-orrery': 'The Orrery', 'star-map': 'Star Map', meteorite: 'Meteorite Fragment' }; + + /* This is a read-only MIRROR of authoritative state, not a second copy of + * exhibit state: it is discarded and re-seeded from a fresh snapshot on + * every attach, and every write attempt is a mutate() call routed back + * through the owner's canonical mutation path -- this document never + * writes to `latest` in response to a user action, only in response to + * a snapshot or an event relayed from the one Core. */ + var latest = null; + + function render() { + if (!latest) return; + Shell.setText(el('artifact-name'), ARTIFACT_LABELS[latest['artifact.selected']] || latest['artifact.selected']); + Shell.setText(el('lighting-readout'), Shell.formatPercent(latest['lighting.level'])); + Shell.setText(el('rotation-readout'), latest['rotation.speed'].toFixed(1) + 'x'); + } + + var link = window.MuseumGallerySurfaceBus.attach({ + timeoutMs: 1500, + onSnapshot: function (snapshot) { + latest = snapshot.values; + Shell.setText(statusEl, 'Attached to Control Room.'); + statusEl.classList.remove('is-waiting'); + cycleButton.disabled = false; + render(); + }, + onEvent: function (event) { + if (!latest) return; + if (event.type === 'state.changed' || event.type === 'selection.changed') { + latest[event.target] = event.value; + render(); + } else if (event.type === 'action.executed' && event.target === 'action.spotlight-flash') { + announcer.flash('Spotlight flashed.'); + } + }, + onTimeout: function () { + Shell.setText(statusEl, 'Waiting for the Control Room surface to be open…'); + statusEl.classList.add('is-waiting'); + cycleButton.disabled = true; + } + }); + + cycleButton.addEventListener('click', function () { + if (!latest) return; + var idx = ARTIFACT_ORDER.indexOf(latest['artifact.selected']); + var next = ARTIFACT_ORDER[(idx + 1) % ARTIFACT_ORDER.length]; + /* Routed to the owner's canonical mutation path -- this is the required + * proof that a native interaction on a non-primary surface obeys the + * same mutation/revision/source/event semantics as primary UI or a + * contract set/invoke (Contract 5.3 Section 31.7). */ + link.mutate('set', 'artifact.selected', next); + }); + + window.addEventListener('beforeunload', function () { link.detach(); }); + window.__museumGalleryDebug = { link: link }; +})(); diff --git a/test-fixtures/reference-exhibits/museum-gallery/artifact.html b/test-fixtures/reference-exhibits/museum-gallery/artifact.html new file mode 100644 index 0000000..5c91eab --- /dev/null +++ b/test-fixtures/reference-exhibits/museum-gallery/artifact.html @@ -0,0 +1,41 @@ + + +
+ + +—
+Lighting: --
+Rotation: --
+ +Attaching…
+—
+Lighting:
+Rotation:
+Labels:
+Attached non-primary surfaces: 0
+Redirecting to the Control Room (primary surface)…
+ + diff --git a/test-fixtures/reference-exhibits/museum-gallery/info-wall.boot.js b/test-fixtures/reference-exhibits/museum-gallery/info-wall.boot.js new file mode 100644 index 0000000..605a50f --- /dev/null +++ b/test-fixtures/reference-exhibits/museum-gallery/info-wall.boot.js @@ -0,0 +1,61 @@ +/* + * Museum Gallery — Information Wall boot (non-primary surface). + * + * Structurally identical in principle to artifact.boot.js: no Core here, + * only an attachment to the Control Room's Core over surface-bus.js, and a + * clear waiting state if that Core is not reachable. + */ +(function () { + 'use strict'; + + var Shell = window.XZBTShell; + var el = Shell.el; + var statusEl = el('status'); + var announcer = new Shell.Announcer({ node: el('announcement'), idleText: 'Waiting for shared state.' }); + var toggleButton = el('toggle-button'); + + var ARTIFACT_LABELS = { 'the-orrery': 'The Orrery', 'star-map': 'Star Map', meteorite: 'Meteorite Fragment' }; + + var latest = null; // read-only mirror; see artifact.boot.js for the rationale + + function render() { + if (!latest) return; + Shell.setText(el('artifact-name'), ARTIFACT_LABELS[latest['artifact.selected']] || latest['artifact.selected']); + Shell.setText(el('labels-readout'), latest['labels.enabled'] ? 'Labels: ON' : 'Labels: OFF'); + } + + var link = window.MuseumGallerySurfaceBus.attach({ + timeoutMs: 1500, + onSnapshot: function (snapshot) { + latest = snapshot.values; + Shell.setText(statusEl, 'Attached to Control Room.'); + statusEl.classList.remove('is-waiting'); + toggleButton.disabled = false; + render(); + }, + onEvent: function (event) { + if (!latest) return; + if (event.type === 'state.changed' || event.type === 'selection.changed') { + latest[event.target] = event.value; + render(); + } else if (event.type === 'action.executed' && event.target === 'action.spotlight-flash') { + announcer.flash('Spotlight flashed.'); + } + }, + onTimeout: function () { + Shell.setText(statusEl, 'Waiting for the Control Room surface to be open…'); + statusEl.classList.add('is-waiting'); + toggleButton.disabled = true; + } + }); + + toggleButton.addEventListener('click', function () { + if (!latest) return; + /* Same canonical mutation path proof as the Artifact Display's cycle + * button, exercising a boolean state target instead of a selection. */ + link.mutate('set', 'labels.enabled', !latest['labels.enabled']); + }); + + window.addEventListener('beforeunload', function () { link.detach(); }); + window.__museumGalleryDebug = { link: link }; +})(); diff --git a/test-fixtures/reference-exhibits/museum-gallery/info-wall.html b/test-fixtures/reference-exhibits/museum-gallery/info-wall.html new file mode 100644 index 0000000..0c7bb4d --- /dev/null +++ b/test-fixtures/reference-exhibits/museum-gallery/info-wall.html @@ -0,0 +1,40 @@ + + + + + +—
+--
+ +Attaching…
++ Opens the three surfaces as three separate same-origin documents (iframes, + one per surface — a genuine multi-document, multi-BroadcastChannel-endpoint + test, not a simulation) and drives the required proof points listed in the + Step 6.2 brief. Every wait below is condition-based (polls a real signal — + attachment state, DOM content, or stateRevision — until it is true or a + timeout elapses); nothing is timed by a fixed sleep standing in for an + actual event. Click "Run checks" once all three frames have loaded. +
+ + +