Step 6.6 — Museum Gallery end-to-end validation through the NGN operator UI
Server: pre-existing `npm start` server (node server/serve.js) on http://127.0.0.1:4173,
        started before this session and left running. A second instance started for
        validation failed with EADDRINUSE and exited; the served state was unchanged.
NGN UI: http://127.0.0.1:4173/
Exhibit: http://127.0.0.1:4173/test-fixtures/reference-exhibits/museum-gallery/control.html
Replacement exhibit: http://127.0.0.1:4173/test-fixtures/reference-exhibits/scifi/index.html
Method: live browser session against the served page; values read from the live DOM, the
exhibit frames, and NGN's own diagnostics panes. No test doubles.

--------------------------------------------------------------------
1. Initial connection (Load exhibit)
--------------------------------------------------------------------
status: connected · synchronized
identity: Museum Gallery · 0.1.0 · Contract 5.3
metadata: { sessionId: "sess-1f54f",
            contract: { major: 5, minor: 3 },
            exhibit: { product: "Museum Gallery", version: "0.1.0", build: "reference-exhibit" },
            registryRevision: 1, stateRevision: 0, sequence: null }
capabilities: render: ready
targets (5): artifact.selected (selection, r/w), lighting.level (range, r/w),
             rotation.speed (range, r/w), labels.enabled (state, r/w),
             action.spotlight-flash (impulse, not readable, not writable)
state: { "artifact.selected": "the-orrery", "lighting.level": 0.6,
         "rotation.speed": 0.4, "labels.enabled": true }
error log: one SESSION entry, no errors

--------------------------------------------------------------------
2. Surface discovery and primary handling
--------------------------------------------------------------------
Presentation surfaces (3):
  surface.control   Control Room      Primary      Role: control      URL: control.html
  surface.artifact  Artifact Display  Non-primary  Role: ambient      URL: artifact.html
  surface.info-wall Information Wall  Non-primary  Role: information  URL: info-wall.html
Primary card buttons: Open disabled, Reload disabled, Close disabled
Primary card status: "Open in the authoritative control frame. Use Connection controls to
reload or disconnect."
Non-primary panes rendered and each reported: "Attached to Control Room."
Control Room readout "Attached non-primary surfaces": 2

--------------------------------------------------------------------
3. Lifecycle (participant count in the authoritative Control Room)
--------------------------------------------------------------------
open both      -> 2
reload         -> 2
reload again   -> 2
close one      -> 1
reopen         -> 2
Reload freshness probe: a marker written to the secondary frame contentWindow was absent
after reload (fresh document), and the document re-attached and re-rendered current state.

--------------------------------------------------------------------
4. Shared-state synchronization
--------------------------------------------------------------------
Control -> secondaries
  Control Room combobox -> "star-map":
    event { type: "selection.changed", sequence: 1, stateRevision: 1,
            target: "artifact.selected", value: "star-map", source: "ui" }
    Control Room "Star Map"; Artifact Display "Star Map"; Information Wall "Star Map"
  Control Room lighting range -> 0.25:
    event { type: "state.changed", sequence: 2, stateRevision: 2,
            target: "lighting.level", value: 0.25, source: "ui" }
    Control Room "25%"; Artifact Display "Lighting: 25%"

Secondary -> primary -> secondaries
  Real mouse click on Artifact Display "Cycle artifact" (authoritative value "star-map"):
    Control Room combobox became "meteorite"; Control Room label "Meteorite Fragment"
    event { type: "selection.changed", sequence: 3, stateRevision: 3,
            target: "artifact.selected", value: "meteorite", source: "ui" }
    Information Wall -> "Meteorite Fragment"; Artifact Display re-rendered authoritative result

Sustained convergence (both secondaries open, alternating origins)
  step 1 control select the-orrery   -> all views "The Orrery"      revision 7  count 2
  step 2 info-wall toggle labels     -> labels.enabled true          revision 8  count 2
  step 3 control rotation 0.6        -> rotation.speed 0.6           revision 9  count 2
  step 4 artifact-display cycle      -> artifact.selected star-map   revision 10 count 2
  Control Room, Artifact Display, Information Wall and NGN reported state all agreed
  after every step.

Late join
  close Information Wall -> count 1
  mutate: rotation 1.7, labels OFF -> stateRevision 5
  reopen Information Wall -> immediately rendered "Meteorite Fragment" / "Labels: OFF",
  count back to 2

Reload recovery
  mutate: lighting 0.9 -> stateRevision 6
  reload Artifact Display -> fresh document returned with "Meteorite Fragment",
  "Lighting: 90%", "Rotation: 1.7x" (current state, not defaults); count still 2

--------------------------------------------------------------------
5. NGN host commands
--------------------------------------------------------------------
Refresh state (state.get): succeeded; stateRevision 10, status connected · synchronized,
                           no error
set lighting.level = 0.35: succeeded; Control Room "35%" (range 0.35),
                           Artifact Display "Lighting: 35%";
                           event { type: "state.changed", sequence: 11, stateRevision: 11,
                                   target: "lighting.level", value: 0.35,
                                   source: "host", correlationId: "ngn-2-4" }
set artifact.selected = meteorite: succeeded; Control Room / Artifact Display /
                           Information Wall all "Meteorite Fragment";
                           event { type: "selection.changed", sequence: 12, stateRevision: 12,
                                   target: "artifact.selected", value: "meteorite",
                                   source: "host", correlationId: "ngn-2-6" }
invoke action.spotlight-flash: succeeded;
                           event { type: "action.executed", sequence: 13,
                                   target: "action.spotlight-flash", args: {},
                                   source: "host", correlationId: "ngn-2-8" };
                           exhibit gallery.spotlightFlashCount incremented;
                           Control Room and both secondaries announcement nodes flashed
                           "Spotlight flashed." (captured by a DOM mutation observer on the
                           Artifact Display announcement node: seen transitions
                           ["Spotlight flashed.", "Waiting for shared state."])

--------------------------------------------------------------------
6. Reconnect and exhibit replacement
--------------------------------------------------------------------
Disconnect:
  status: disconnected · not synchronized
  catalog: "Connect an exhibit to discover its controls."
  surfaces: "No presentation surfaces advertised."; surface cards 0; surface iframes 0
  control frame removed; state {}; metadata all null; no error entries
Reconnect:
  status: connected · synchronized; session sess-15cf1; contract { major: 5, minor: 3 }
  targets (5); surfaces (3) with surface.control primary; participant count 0
  no stale panes; both secondaries reopened -> "Attached to Control Room.", count 2
Switch to SciFi:
  identity: SciFi-XZBT · 5.2.0 · Contract 5.3
  metadata: sessionId "sess-2akzu3a7", contract { major: 5, minor: 3 },
            registryRevision 1, stateRevision 0
  status: connected · synchronized; targets (61); surfaces: none advertised
  iframes referencing museum-gallery: 0
Switch back to Museum Gallery:
  session sess-126c6; contract { major: 5, minor: 3 }; targets (5); surfaces (3) with
  correct primary; participant count 0; iframes referencing scifi: 0
  state: fresh exhibit defaults, stateRevision 0

--------------------------------------------------------------------
7. Failure isolation (live)
--------------------------------------------------------------------
Artifact Display document globals:
  XZBTContractCore: undefined
  MuseumGalleryContract: undefined
  MuseumGalleryExhibit: undefined
  XZBTHostTransport: undefined
  MuseumGallerySurfaceBus: present
  __museumGalleryDebug.core: absent
Forged protocol messages posted directly to the secondary window:
  { xzbt: "5.3", type: "hello", requestId: "probe-secondary", supportedContractMajors: [5] }
  { xzbt: "5.3", type: "set", requestId: "probe-set", sessionId: "x",
    target: "artifact.selected", value: "meteorite" }
  -> responses received: none; authoritative selection unchanged ("The Orrery")
Surface-local failure: Artifact Display frame navigated to
  /test-fixtures/reference-exhibits/museum-gallery/does-not-exist.html (served "Not found")
  -> NGN status stayed "connected · synchronized" on session sess-126c6;
     control frame still control.html; registry/state unaffected; no error entry;
     participant count 2 -> 0 with no stale entry
NGN per-surface Reload on the failed pane:
  -> frame back at artifact.html, "Attached to Control Room.", participant count 1,
     session still synchronized, no error entry

Automated-only isolation evidence (not re-injected live): malformed/invalid descriptor
catalogs, isolated HTTP/frame/reload failures, external frame removal detection, stalled
surface resource, redirect refusal on resource check, presentation-window impersonation
refusal, transport source/origin validation.

--------------------------------------------------------------------
8. Automated verification
--------------------------------------------------------------------
Focused: node --test tests/museum-gallery.test.js tests/local-surfaces.test.js
                   tests/surface-validation.test.js tests/postmessage-interop.test.js
                   tests/connection.test.js
  tests 107 / pass 107 / fail 0

Full: node --test
  tests 129 / pass 129 / fail 0

git diff --check -> clean (exit 0)
python devlog_editor.py --validate -> devlog-editor: devlog is valid (exit 0)
