Skip to content

feat(m8): Canvas-authored multi-blueprint live probe + provenance (LSDP)#52

Open
ClodoCapeo wants to merge 5 commits into
mainfrom
forge/pulsar-44-m8-canvas-live
Open

feat(m8): Canvas-authored multi-blueprint live probe + provenance (LSDP)#52
ClodoCapeo wants to merge 5 commits into
mainfrom
forge/pulsar-44-m8-canvas-live

Conversation

@ClodoCapeo
Copy link
Copy Markdown
Contributor

M8 — end-to-end live test driven by a Canvas-authored (+Blue) scene

Implements Pulsar #44 (rich fixture) + #45 (Blue blueprints) + #46 (probe) + #47 (redaction) per ADR Pulsar-002 (M8) + ADR Orion-001 (multi-blueprint envelope). The on-air frame is a scene this test authors — layout by ZabCanvas, logic by N≥2 distinct Blue blueprints, compiled by Orion, rendered by Solar v0.2.0 over the LSDP wire in Pulsar's CEF, broadcast by Pulsar — and is proven to be that authoring.

What's here

  • scripts/fixtures/m8-blueprint-{score,timer}.json — two distinct, pure Blue blueprints (core.literal@1 / core.math.add@1 / core.output@1), each writing leaf value; bound under distinct scene-local keys score / timer.
  • scripts/fixtures/m8-scene.lsml.json — deterministic rich LSML 1.1 scene: full-frame #1A9E57 background (provenance marker), two bound text labels (bind:{value:"score.value"} / "timer.value" — hand-prefixed per ADR Orion-001 §3.3), a shape. Carries its own scene_version content-address (computed the same way lumencast-go/lsml.HashBundle does).
  • scripts/m8_setup.py — gateway-first SETUP leg: ensure blueprints → PUT bundle to Canvas A0 → save+push (lsml_bundle_hash=H) → drive active-scene (M8 is its first real driver) → round-trip GET /show → mint viewer show-token → compose Solar v0.2.0 LSDP URL (broadcast-url.ts::getSolarSceneUrl parity).
  • scripts/probe-m8-canvas-live.py — orchestrator; reuses the proven M6 broadcast core (probe-m6-live.py: CEF spawn/reap, PNG decode, analyse_frame, RTMP metrics, bounded StartDestination retry) and extends the non-blank pre-flight with the modal-colour provenance assertion.
  • scripts/run-m8.ps1 — wrapper with a hard grep-assert: fails the run if any credential leaks to stdout/PNG/VOD, regardless of probe exit.
  • pipeline.yml — gated, non-blocking m8-provenance CI job mirroring the M6 shape (typed skip exit 3 on LIGHT build).

How provenance is proven

  1. Round-trip (server-side, deterministic): after push+active-scene, GET /orion/api/v1/showactive_scene_id == scene_id; the push response scene_version is captured + reported.
  2. Modal-colour (pixels↔state): the captured CEF frame's modal colour ≈ the authored #1A9E57 within tolerance. Blank / wrong-colour / fallback ⇒ NO GO (no broadcast), typed diagnosis.

⚠️ Sensitive surface — Bastion clearance required (PV-1 / CC-1)

This PR touches tokens / secrets / network surface (show-token mint, operator JWT, Solar URL, Twitch key). Mitigations in place: zero token committed (no baked-JWT DEFAULT_SOLAR_URL — the M6 trap), redactSolarUrl ported to Python and applied to every solar_url line, operator JWT is M8_OPERATOR_TOKEN (short-TTL admin, étage-1) and hard-refused if equal to ORION_OPERATOR_TOKEN, grep-assert gate. detect-secrets + manual scan clean on all new files.

Not run here (separate validation step — Probe / Keeper)

The real go-live is not run by this PR (the stack is mid PG rotation; the broadcast leg needs the deployed VPS + Twitch key + the étage-1 operator token). Validated locally: py_compile green on all modules, pure-helper asserts (hash determinism, redaction parity, URL composition, fixture validity) pass, config-error/CC-1 guard paths return exit 2, YAML valid, secret scans clean.

Refs #44 #45 #46 #47

🤖 Generated with Claude Code

ClodoCapeo and others added 5 commits June 8, 2026 05:34
Checked-in fixtures for the M8 Canvas-authored live test: two distinct,
pure Blue blueprints (score: literal+literal -> math.add -> output leaf
`value`; timer: literal -> output leaf `value`) and a rich multi-component
LSML 1.1 scene bundle. The scene binds both blueprints by scene-local key
via hand-prefixed bindings (`bind:{value:"score.value"}` / `"timer.value"`,
ADR Orion-001 §3.3), over a full-frame solid background of a known unusual
colour (#1A9E57) that the pre-flight modal-colour provenance check ties the
on-air pixels to. The bundle carries its own content-address scene_version
(sha256:c2e528...) computed the same way lumencast-go/lsml.HashBundle does,
so Canvas's A0 store address-check passes and /layouts/{H} resolves.

N >= 2 distinct blueprint ids + distinct keys; computes are core.* pure so
Orion's compile is clean by construction (no IMPURE_COMPUTE/COMPILE_FAILED).

Refs #44 #45

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
probe-m8-canvas-live.py authors a multi-blueprint Canvas+Blue scene, pushes
it through the real Orion compile, drives the active-scene (M8 is the FIRST
real driver of POST /orion/api/v1/show/active-scene), mints a fresh viewer
show-token, and runs the proven M6 pre-flight + broadcast core against the
Solar v0.2.0 LSDP URL it composed itself — then PROVES the on-air frame is
that authoring.

SETUP leg (m8_setup.py, gateway-first HTTP, stdlib-only):
  - ensure N Blue blueprints (idempotent by slug, published)
  - PUT the LSML bundle to Canvas A0, save + push (lsml_bundle_hash=H)
  - active-scene + round-trip GET /show (active_scene_id == scene_id)
  - mint show-token, compose Solar v0.2.0 LSDP URL (broadcast-url.ts parity)

Provenance: (3) server-side round-trip active_scene_id == scene_id + the
captured push scene_version, and (1) the captured CEF frame's modal colour
≈ the authored background (#1A9E57) — binds server state to pixels. Blank /
wrong-colour => NO GO (no broadcast). Reuses M6's CEF spawn/reap, PNG
decode, analyse_frame, RTMP metrics + bounded StartDestination retry by
importing probe-m6-live.py; extends the non-blank predicate with the
modal-colour assertion.

Security (Bastion PV-1/CC-1): zero token committed (no baked-JWT
DEFAULT_SOLAR_URL — the M6 trap); redactSolarUrl ported to Python and
applied to every solar_url line; operator JWT is M8_OPERATOR_TOKEN
(short-TTL admin, étage-1) and is hard-refused if equal to
ORION_OPERATOR_TOKEN; run-m8.ps1 grep-asserts no credential leaks to
stdout/PNG/VOD and fails the run on a leak regardless of probe exit. A
gated, non-blocking CI job mirrors the M6 shape (typed skip exit 3).

Wire = LSDP by default (--show-stream-path stream.lsdp), bespoke `stream`
kept as a supported fallback.

Refs #44 #46 #47

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Override m6.LIVE_VOD_DIR / PROOF_PNG / BUILD_DIR / DESTINATION_NAME before
spawn so the reused broadcast core writes the M8 proof PNG, VOD, and uses an
M8-specific Twitch destination name (clean teardown, no M6/M8 artefact
collision). PulsarProcess.spawn reads LIVE_VOD_DIR for PULSAR_RECORD_DIR, so
the override must precede spawn — it now does.

Refs #46

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…broadcast

Vigil request-changes: the M8 docstring claimed the broadcast leg reused the
bounded anti-boot-race StartDestination retry, but m6.broadcast (reused verbatim
by M8) did a single-shot StartDestination. The post-scene-switch boot race was
observed flaking in CI, so make the claim true by adding the robustness, not by
softening the doc.

Factor start_destination_with_retry() into probe-m6-live.py (the shared
broadcast core M8 imports), ported verbatim from probe-twitch-live.py's
START_DEST_* pattern: budget 20s, cadence 1s, retry ONLY on the exact transient
'frontend streaming output unavailable' error, hard-fail on any other error or
on an exhausted budget, zero masking. m6.broadcast now goes live through it, so
the M8 broadcast path inherits the retry with no fork of the broadcast loop.

Correct the M8 docstring + the broadcast comment to describe what is actually
wired, and fix the truncated grep-assert comment in run-m8.ps1 (cosmetic).
Redaction / grep-assert / provenance / legs left untouched.

Refs #46

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The S7 provenance round-trip (get_show) called GET /orion/api/v1/show with
auth=False. On the public gateway (zabgate.cyell.dev) this endpoint is
operator-gated: 401 without a Bearer, 200 with the operator Bearer (verified
by Keeper). So S7 raised SetupError(401) before pre-flight and the SETUP
never reached broadcast.

Flip get_show to auth=True so it rides the operator JWT like every other
SETUP leg; expect=(200,) unchanged. Audited all SETUP legs: S5 push and S6
active-scene are operator-gated mutations and already auth=True (the _request
default); the LSDP WS URL keeps the viewer show-token in ?token= and never
carries the operator Bearer. S7 was the only leg with the bug.

Refs #45 #46

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant