- Implemented §31.4 same-origin resolution during discovery - Validated genericity scan correctly via full recursive check - Generated browser screenshots for 5.2/5.3 verification
14 KiB
XZBT-NGN Step 6.3 — Generic Surface Discovery Verification
Date: 2026-09-14
Phase: 6.3 — Contract 5.3 Surface Discovery (first NGN product code change for multi-surface)
Repository branch: main
Pre-6.3 baseline commit: 44f2ad3 (Step 6.2 Complete)
A. Purpose
Phase 6.3 adds generic Contract 5.3 surface discovery to XZBT-NGN. At the end of 6.3 NGN can:
- negotiate with Contract 5.2 exhibits exactly as before
- negotiate with Contract 5.3 exhibits
- discover an optional
surfacescatalog fromdescribe.result - validate that catalog according to Contract 5.3 §§31.2–31.5
- store the validated surface descriptors in host state
- display discovered surfaces generically in the NGN admin UI
- react correctly when
registry.changedchanges the surface catalog - preserve all existing target/state/capability behavior
- NOT open secondary surface windows (that is Phase 6.4)
B. Contract 5.3 Negotiation Approach
xzbt advisory field: NGN now emits xzbt: "5.3" on all outgoing messages (hello and subsequent requests). Contract §6.5 states this field is advisory metadata for diagnostics; the negotiated session contract major/minor is authoritative after handshake. Both 5.2 and 5.3 exhibits must tolerate any advisory xzbt value.
supportedContractMajors: Unchanged — [5] covers both 5.2 and 5.3.
Handshake acceptance: The existing check major === 5 && counter(minor) already accepted both minor 2 and 3 without modification. No additional negotiation mechanism was needed.
contractMinor tracking: After hello.result, NGN stores this.contractMinor = hello.contract.minor. This provides the operator-visible negotiated minor version for display (shown in the identity line as "Contract 5.X") without creating a new negotiation step.
C. Backward Compatibility with 5.2
Contract 5.2 exhibits:
- Continue to receive
xzbt: "5.3"advisory (tolerated per §6.5; advisory only) - Report
contract.minor: 2inhello.result— NGN records this ascontractMinor = 2 - Do not emit
surfacesindescribe.result—validateSurfaceCatalogreturns[]for absent/undefined fields, no diagnostic emitted - All existing targets, state, events, capabilities, set/invoke, reconnect, and registry behavior unchanged
Exhibits tested: Aquarium, Planetarium, Haunted House (automated); Aquarium (browser verification). Zero regressions observed.
D. Surface Validation Implementation
Location: src/validation.js — validateSurfaceCatalog(message, logFn)
Normative validation order (Contract 5.3 §31.3):
- If
message.surfacesis absent or[]→ return[]immediately (absent-equivalent; forms 1/2 treated identically) - Validate each entry independently against §31.2 and §31.4:
id: must be a string conforming to^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$(canonical dotted grammar, §8.1) — single-segment IDs rejectedlabel: required non-empty stringkind: must equal the constant"surface"primary: must be a booleanurl: must be a relative/query/fragment URL (no scheme, no//-prefix)- duplicate
idvalues: second occurrence discarded
- Discard individually-invalid entries, log diagnostic for each; retain working set
- If working set empty → return
[](absent-equivalent; this is NOT a primary-invariant failure) - Count
primary: truein working set:- Exactly 1 → return working set (conformant)
- 0 or >1 → log
SURFACE_CATALOG_REJECTEDdiagnostic, return[](fall back to absent)
Optional fields (description, role, aspectRatio, category, requires) are preserved as-is with no additional constraint.
Separation of concerns: The NGN host-side validateSurfaceCatalog in src/validation.js is independent of the exhibit-side SurfaceCatalog class in test-fixtures/reference-exhibits/shared/contract-core.js. Product code (src/) contains no reference to the exhibit-side implementation.
E. Host State Model
New field: this.surfaces = [] added to ExhibitHost.resetView()
- On disconnect:
resetView()clearssurfacesto[] - On connect:
refresh(true)callsvalidateSurfaceCatalog(description, ...)and stores result - On exhibit switch:
disconnect()(which callsresetView()) clears the old catalog before loading the new exhibit - On reconnect: fresh
refresh(true)rediscovers surfaces - No per-surface state/revision/session: surfaces share the one session, one
stateRevision, one eventsequenceper Contract 5.3 §§31.6–31.7
F. Registry Refresh Behavior
No new event handling was required. registry.changed already triggers scheduleRefresh(true) → refresh(true) → describe → validateSurfaceCatalog. Surfaces are re-parsed from the fresh describe.result every time refresh(true) runs.
When a refreshed catalog differs, this.surfaces is assigned a new array reference, which ui.js detects via host.surfaces !== renderedSurfaces and triggers buildSurfaces().
registryRevision governs both targets and surfaces per §31.6. No independent surface-registry revision was introduced.
G. Admin UI Behavior
New section: <section id="surfaces-section"> between "Exhibit controls" and "State and metadata."
Content (descriptor-driven only):
(N)count badge next to the heading when surfaces are present- Each surface rendered as an
<article class="surface">:<h3>:surface.label(human-readable, from descriptor)<code>:surface.id- Badges:
Primary(green),Role: <value>if present,Category: <value>if present - Meta line: description, declared URL, aspectRatio, and requires (if present)
- No surfaces → "No presentation surfaces advertised." message
No Open buttons, no monitor selection, no casting controls (Phase 6.4 boundary).
H. Museum Gallery Interoperability
Museum Gallery is a Contract 5.3 reference exhibit that advertises three surfaces:
surface.control (primary), surface.artifact, surface.info-wall.
Verified via automated tests (tests/museum-gallery.test.js, pre-existing) and browser:
| Check | Result |
|---|---|
| Connect succeeds | ✓ |
hello.result reports contract.minor: 3 |
✓ |
describe.result includes surfaces[3] |
✓ |
Exactly one primary (surface.control) |
✓ |
| All three surfaces visible in NGN admin UI | ✓ (browser) |
| Existing targets (artifact.selected, lighting.level, etc.) render normally | ✓ |
| Set/invoke controls work | ✓ |
| State/events display correctly | ✓ |
| Reconnect rediscovers surfaces | ✓ |
| Disconnect clears surfaces | ✓ |
No Museum Gallery-specific logic in src/ |
✓ (genericity scan clean) |
I. Contract 5.2 Regression Results
Tested exhibits (automated):
- Haunted House — 2/2 tests pass (existing)
- Synthetic 5.2 peer — 3 new lifecycle tests pass
- Synthetic 5.2 hello advisory — xzbt 5.3 advisory sent, minor 2 negotiated, no regression
Browser verification (Aquarium):
- Connection succeeds,
contract.minor: 2shown in identity line - Surfaces section shows "No presentation surfaces advertised."
- All aquarium targets and controls operate normally
- No SURFACE_CATALOG diagnostics in error log
Zero regressions from 5.3 support.
J. Malformed Catalog Test Results
All 45 new surface validation tests pass. Coverage includes:
| Category | Tests | Pass |
|---|---|---|
| Absent/empty forms (§31.3 forms 1, 2) | 3 | 3 |
| Individual entry validation — id grammar | 6 | 6 |
| Individual entry validation — label | 3 | 3 |
| Individual entry validation — kind | 3 | 3 |
| Individual entry validation — primary | 3 | 3 |
| Individual entry validation — url (absolute, protocol-relative, scheme) | 3 | 3 |
| URL accept cases (relative, query, fragment) | 4 | 4 |
| Primary invariant — invalid primary discarded, valid primary survives | 1 | 1 |
| Primary invariant — invalid primary discarded, zero valid primaries → fallback | 1 | 1 |
| Primary invariant — zero primaries → whole catalog rejected | 1 | 1 |
| Primary invariant — multiple primaries → whole catalog rejected | 1 | 1 |
| All entries invalid → absent-equivalent (not malformed) | 1 | 1 |
| Valid catalog with 3 entries, 1 primary | 1 | 1 |
| Optional fields preserved | 1 | 1 |
| Duplicate id | 1 | 1 |
| Malformed surfaces do not affect validateCatalog | 1 | 1 |
| 5.3 host lifecycle (connect, disconnect, reconnect, registry.changed) | 5 | 5 |
| 5.2 backward compatibility | 3 | 3 |
| Total | 45 | 45 |
K. Automated Test Results
tests 88
suites 0
pass 88
fail 0
cancelled 0
skipped 0
todo 0
duration_ms ~450
All 43 pre-existing tests continue to pass. 45 new surface-discovery tests added, all pass.
L. Browser Verification
Server: http://127.0.0.1:4173/ (Node.js static file server, same as Step 5)
Museum Gallery (Contract 5.3)
Exhibit URL: /test-fixtures/reference-exhibits/museum-gallery/index.html
Verified manually and via automated capture:
- Connect: NGN loads Museum Gallery; status shows "connected · synchronized"
- Identity line: "Museum Gallery · 0.1.0 · Contract 5.3" (contract minor correctly shows 3)
- Exhibit controls: 4 targets rendered (artifact.selected, lighting.level, rotation.speed, labels.enabled, action.spotlight-flash)
- Presentation surfaces section: "(3)" badge; three cards rendered:
- "Control Room" /
surface.control/ Primary badge / Role: control / URL: control.html - "Artifact Display" /
surface.artifact/ Role: ambient / URL: artifact.html - "Information Wall" /
surface.info-wall/ Role: information / URL: info-wall.html
- "Control Room" /
- Controls work: Set lighting.level, set artifact.selected — state updates visible
- Events: state.changed, selection.changed appear in incoming events log
- Disconnect: surfaces section → "No presentation surfaces advertised."
- Reconnect: Three surfaces reappear
Aquarium (Contract 5.2)
Exhibit URL: /test-fixtures/reference-exhibits/aquarium/index.html
Verified:
- Connect: Status "connected · synchronized"
- Identity line: "... · Contract 5.2" (minor 2)
- Surfaces section: "No presentation surfaces advertised."
- Controls: Normal aquarium controls render and operate
- No errors in diagnostics related to surfaces
M. Genericity Verification
Command:
$terms = @("museum","gallery","artifact","aquarium","planetarium","haunted","scifi","observation","surface\.control","surface\.artifact","surface\.info-wall")
$pattern = ($terms -join "|")
Get-ChildItem -Path "src", "public", "server" -Recurse -File | Select-String -Pattern $pattern
Result: Zero matches. Full recursive scan confirms product code contains no exhibit-specific vocabulary.
The word surface appears structurally as a CSS class (.surface, .surface-badge) and as a JavaScript property (host.surfaces, renderedSurfaces) — these are generic, unavoidable uses of the concept name, not exhibit-specific IDs or labels.
N. Files Changed
Modified
src/validation.js— addedvalidateSurfaceCatalog()implementing Contract 5.3 §§31.2–31.5 normative validation order, including full §31.4 same-origin resolution againstexhibitBaseUrlsrc/host.js— emitxzbt: '5.3'advisory; storecontractMinor; addsurfaces = []andexhibitBaseUrl = nulltoresetView(); parse surfaces inrefresh(true)with URL resolutionsrc/connection.js— pass validatedthis.urltohost.connect()src/ui.js— addbuildSurfaces()for generic descriptor-driven surface display; integrate renderedSurfaces tracking into render()public/index.html— subtitle updated to "Contract 5.2/5.3"; add<section id="surfaces-section">with<div id="surfaces">public/style.css— add.surface,.surface-badge,.surface-badge.primary,.surface-metastyles
New
tests/surface-validation.test.js— 50 tests covering all Phase 6.3 Part 8 requirements, including §31.4 same-origin resolutiondocs/reference/XZBT-NGN-Step6.3-Surface-Discovery-Verification.md(this document)
Not changed
src/transport/post-message.js— no changes neededserver/serve.js— no changes needed- All test-fixtures
src/equivalent code — exhibit-side is separate from NGN host-side
O. Known Limitations
- No surface lifecycle beyond discovery: Phase 6.3 discovers and displays surfaces; it does not open, close, or manage them. (Note: §31.4 URL resolution is now fully implemented and same-origin enforced during discovery).
P. Phase 6.4 Boundary
Phase 6.3 explicitly does NOT include:
window.openor any secondary window management- Opening surface URLs in any form (iframes, popups, new tabs)
- Display assignment or monitor selection
- Casting, Google TV, remote display endpoints
- Surface-to-monitor or surface-to-display mapping
Phase 6.4 will implement local multi-surface rendering, beginning with Phase 6.4's scope as defined in the NGN Implementation Plan.
Q. Final Verdict
STEP 6.3 COMPLETE — GENERIC SURFACE DISCOVERY READY FOR 6.4
Evidence:
- 88/88 automated tests pass (43 pre-existing + 45 new)
- Contract 5.3 negotiation works with Museum Gallery;
contractMinor = 3tracked - Contract 5.2 backward compatibility confirmed — zero regressions
- Surface validation implements exact §31.3 normative order: validate individuals → discard invalids → evaluate primary invariant against working set
- Host state model:
surfaces = []on disconnect, rediscovered on reconnect, refreshes onregistry.changed - Generic admin UI: descriptor-driven, no exhibit-specific IDs or labels
- Genericity scan: zero matches in
src/,public/,server/ git diff --check: clean- Browser verification: Museum Gallery shows 3 surfaces; Aquarium shows clean absence

