Files
XZBT-NGN/docs/reference/XZBT-NGN-Step6.3-Surface-Discovery-Verification.md
T
Labyricorn ed76cf6189 Step 6.3 Follow-up: Implement §31.4 full URL resolution
- 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
2026-09-14 14:36:07 -07:00

14 KiB
Raw Blame History

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:

  1. negotiate with Contract 5.2 exhibits exactly as before
  2. negotiate with Contract 5.3 exhibits
  3. discover an optional surfaces catalog from describe.result
  4. validate that catalog according to Contract 5.3 §§31.231.5
  5. store the validated surface descriptors in host state
  6. display discovered surfaces generically in the NGN admin UI
  7. react correctly when registry.changed changes the surface catalog
  8. preserve all existing target/state/capability behavior
  9. 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: 2 in hello.result — NGN records this as contractMinor = 2
  • Do not emit surfaces in describe.resultvalidateSurfaceCatalog returns [] 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.jsvalidateSurfaceCatalog(message, logFn)

Normative validation order (Contract 5.3 §31.3):

  1. If message.surfaces is absent or [] → return [] immediately (absent-equivalent; forms 1/2 treated identically)
  2. 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 rejected
    • label: required non-empty string
    • kind: must equal the constant "surface"
    • primary: must be a boolean
    • url: must be a relative/query/fragment URL (no scheme, no //-prefix)
    • duplicate id values: second occurrence discarded
  3. Discard individually-invalid entries, log diagnostic for each; retain working set
  4. If working set empty → return [] (absent-equivalent; this is NOT a primary-invariant failure)
  5. Count primary: true in working set:
    • Exactly 1 → return working set (conformant)
    • 0 or >1 → log SURFACE_CATALOG_REJECTED diagnostic, 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() clears surfaces to []
  • On connect: refresh(true) calls validateSurfaceCatalog(description, ...) and stores result
  • On exhibit switch: disconnect() (which calls resetView()) 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 event sequence per Contract 5.3 §§31.631.7

F. Registry Refresh Behavior

No new event handling was required. registry.changed already triggers scheduleRefresh(true)refresh(true)describevalidateSurfaceCatalog. 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).


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: 2 shown 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)

Exhibit URL: /test-fixtures/reference-exhibits/museum-gallery/index.html

Museum Gallery surfaces

Verified manually and via automated capture:

  1. Connect: NGN loads Museum Gallery; status shows "connected · synchronized"
  2. Identity line: "Museum Gallery · 0.1.0 · Contract 5.3" (contract minor correctly shows 3)
  3. Exhibit controls: 4 targets rendered (artifact.selected, lighting.level, rotation.speed, labels.enabled, action.spotlight-flash)
  4. 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
  5. Controls work: Set lighting.level, set artifact.selected — state updates visible
  6. Events: state.changed, selection.changed appear in incoming events log
  7. Disconnect: surfaces section → "No presentation surfaces advertised."
  8. Reconnect: Three surfaces reappear

Aquarium (Contract 5.2)

Exhibit URL: /test-fixtures/reference-exhibits/aquarium/index.html

Aquarium absent surfaces

Verified:

  1. Connect: Status "connected · synchronized"
  2. Identity line: "... · Contract 5.2" (minor 2)
  3. Surfaces section: "No presentation surfaces advertised."
  4. Controls: Normal aquarium controls render and operate
  5. 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 — added validateSurfaceCatalog() implementing Contract 5.3 §§31.231.5 normative validation order, including full §31.4 same-origin resolution against exhibitBaseUrl
  • src/host.js — emit xzbt: '5.3' advisory; store contractMinor; add surfaces = [] and exhibitBaseUrl = null to resetView(); parse surfaces in refresh(true) with URL resolution
  • src/connection.js — pass validated this.url to host.connect()
  • src/ui.js — add buildSurfaces() 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-meta styles

New

  • tests/surface-validation.test.js — 50 tests covering all Phase 6.3 Part 8 requirements, including §31.4 same-origin resolution
  • docs/reference/XZBT-NGN-Step6.3-Surface-Discovery-Verification.md (this document)

Not changed

  • src/transport/post-message.js — no changes needed
  • server/serve.js — no changes needed
  • All test-fixtures src/ equivalent code — exhibit-side is separate from NGN host-side

O. Known Limitations

  1. 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.open or 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 = 3 tracked
  • 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 on registry.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