test(m9): Blue-trigger reactive live-repaint probe + fixtures (#53)#55
Open
ClodoCapeo wants to merge 5 commits into
Open
test(m9): Blue-trigger reactive live-repaint probe + fixtures (#53)#55ClodoCapeo wants to merge 5 commits into
ClodoCapeo wants to merge 5 commits into
Conversation
Deterministic, checked-in fixtures for the M9 proof (Blue ADR 001 §4): - m9-blueprint-bg.json: a Blue passthrough blueprint (event→output, single core.input `colour`) whose /trigger maps outputs to the scoped leaf __inputs.blue.pulsar-m9-bg.colour. Stdlib-only nodes → publishes on any deployed Blue; never compiled by Orion (runs in Blue's interpreter). - m9-scene.lsml.json: an LSML 1.1 bundle whose frame BACKGROUND binds that leaf, declared as an operator_input with default colour A (#1A9E57) so Orion seeds A on boot and accepts Blue's later in-scope write (sceneAcceptsPath). No Canvas schema change — the existing per-node bind. Colour A (#1A9E57) vs trigger colour B (#C81E5A) are 305 apart in RGB Manhattan, far past the modal tolerance, so the A→B repaint assertion is unambiguous. Verified against the real Blue leaf_mapper (produces the exact bound leaf) and Orion validateBindingKeys (blueprint-free scene → binding passes compile). Refs #53 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Derived from m8_setup.py, retargeted at ADR Blue 001's reactive contract.
Reuses the M8 toolkit wholesale (gateway HTTP client, LSML hash port,
redaction ports, Solar URL composer) by importing m8_setup; only the SETUP
orchestration + the M9 result shape are new.
Differences vs M8: one blueprint (not N≥2); the scene definition carries
blueprints=[] (the visible binds an operator-input leaf, not a blueprint-key
leaf); the provenance target A is read from the scene's operator_inputs
default (the exact declaration that drives the live leaf). Adds fire_trigger:
POST /blue/api/v1/blueprints/{id}/trigger with the operator JWT as an
Authorization: Bearer HEADER (never a query string — ADR Blue 001 R6),
inputs={colour: B}, asserting 200 + the colour passed through to the leaf.
Refs #53
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
probe-m9-canvas-live.py proves Blue ADR 001 §4: a /trigger repaints a live
scene with no reload. Reuses the M6 CEF core (spawn/reap, stdlib PNG decode,
analyse_frame, broadcast) and the M8 non-blank provenance pre-flight.
The proof is capture-A / fire / capture-B on the SAME browser_source:
1. pre-flight → non-blank AND modal ≈ seeded default A (build/m9-before.png);
2. fire the trigger (operator Bearer header) with inputs={colour: B};
3. poll the same source until modal ≈ B within Orion's input→delta budget
(REPAINT_DEADLINE_S) AND Manhattan(B,A) > REPAINT_MIN_DELTA
(build/m9-after.png).
SetCaptureSource is issued once, so a change is a live LSDP repaint, not a
reload. A 200 from /trigger alone does NOT pass — the pixels must move
(best-effort delivery, ADR Blue 001 §3.2.6). Broadcast is opt-in
(--broadcast), so the proof needs no Twitch key.
run-m9.ps1 tees stdout + grep-asserts no credential (the operator JWT — now
also the /trigger credential — the Twitch key, and JWT-shaped show-tokens)
leaks to stdout / the before+after PNGs / VOD. A leak fails the run
regardless of probe exit. UTF-8 no BOM.
Refs #53
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The M9 probe proved the A->B repaint BEFORE going live, so the broadcast
only ever showed a static magenta field -- the green->magenta transition
never appeared on the stream or in the VOD. Reorder to M8's scene-switch
pattern: go live on the green A frame, then fire /trigger at duration/2
WHILE broadcasting so the swap happens in direct.
New live leg broadcast_with_live_trigger(): CreateDestination ->
StartDestination (M6/M8 anti-boot-race retry) on GREEN A -> capture-A on
the live wire -> poll ~duration/2 (green) -> /trigger {colour:B} at mid ->
capture-B on the live wire (assert modal~B, Manhattan(A->B) > floor) ->
poll the magenta half -> StopRecord/StopDestination. The VOD now records
the on-air green->magenta cut.
Live is now the DEFAULT (porteur: a l'antenne par defaut); --no-broadcast
keeps the prove-only mode (pre-flight -> trigger -> capture-B, no Twitch
key) for CI. run-m9.ps1 inverts to -NoBroadcast accordingly. Secret
redaction unchanged: operator JWT / Twitch key / show-token scrubbed,
grep-assert intact. py_compile clean.
Refs #53
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Carry the M9 wave debt (BOM prefix) on its owning branch so it survives the ADR-003 land. Unrelated to ADR 003; belongs to the M9 lot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
M9 — prove a Blue trigger repaints a live scene on air
Closes the last M9 brick (Pulsar #53): the probe that proves
POST /api/v1/blueprints/{id}/triggerrepaints the live scene — theend-to-end proof of Blue ADR 001 §6 criterion 4. Built on the M8 authoring
What this proves
The chain
trigger → Blue interprets → maps outputs → pushes the scoped leaf → Orion recompute → delta → Solar repaint, no reload(ADR Blue 001 §3.2.5),demonstrated on captured pixels:
m9_setup.py, derived fromm8_setup.py) authors a scene whoseframe background is bound to
__inputs.blue.pulsar-m9-bg.colour,declared as an
operator_inputwith default colour A (#1A9E57). Orionseeds A on boot and accepts Blue's later in-scope write (
sceneAcceptsPath).frame is non-blank AND its modal ≈ A →
build/m9-before.png.POST /blue/api/v1/blueprints/{id}/triggerwith the operatorBearer header (never a query string — ADR Blue 001 R6) and
inputs={colour: B}(#C81E5A).browser_sourceuntil modal ≈ B withinOrion's input→delta budget AND
Manhattan(B,A) > 80→build/m9-after.png.SetCaptureSourceis issued once, so the change is a live LSDP repaint, nota reload. A
200from/triggeralone does not pass — the pixels must move(delivery is best-effort, ADR Blue 001 §3.2.6). A=(26,158,87) vs B=(200,30,90)
are 305 apart in RGB Manhattan, far past the modal tolerance (24) and the
repaint floor (80), so the assertion is noise-proof.
Fixture (checked-in, deterministic)
scripts/fixtures/m9-blueprint-bg.json— Blue passthrough blueprint(event→output, single
core.inputcolour); stdlib-only nodes → publisheson any deployed Blue; never compiled by Orion (runs in Blue's interpreter).
scripts/fixtures/m9-scene.lsml.json— LSML 1.1 bundle, framebind.background→ the leaf,
operator_inputs[].default= A. No Canvas schema change (theexisting per-node bind; Solar's
resolvePropsapplies any binding key).Sensitive surface (→ Bastion clearance)
This PR exercises a new auth/network surface (ADR Blue 001 R1–R6): it fires
the operator-gated
/triggerand the Blue→Orion service-token push reacts. Theoperator JWT now also authorises
/triggerand is added to the grep-assertneedle set.
run-m9.ps1fails the run if the operator JWT, the Twitch key, or aJWT-shaped show-token leaks to stdout / the before+after PNGs / VOD. No secret is
committed; all credentials come from étage-1.
Validation done (not the real run)
py -3 -m py_compileon both modules — green.leaf Blue's real
leaf_mapperproduces (__inputs.blue.pulsar-m9-bg.colour);A/B distance 305;
/triggerissues POST withAuthorization: Bearerheader (asserted no
token=in the URL), correct path + body.validateBindingKeys: blueprint-free scene → the__inputs.*bindingpasses compile.
run-m9.ps1parses clean, no BOM.The real run (spawn Pulsar CEF, broadcast/capture) is the separate final
validation step — left to Keeper/Probe.
Stacking
Stacked on #52 (M8): M9 reuses
m8_setup.py+probe-m8-canvas-live.py+probe-m6-live.py, none of which are onmainyet. Base is set toforge/pulsar-44-m8-canvas-liveso this diff is M9-only. Merge #52 first,then rebase/retarget this PR onto
main.Keeper run notes
M8_OPERATOR_TOKEN(étage-1.env.m8, short-TTL admin JWT — drivesSETUP and
/trigger; must not equalORION_OPERATOR_TOKEN), and adeployed Blue with the bridge on (
BLUE_OPERATOR_TOKENset sopushed.delivered=true; otherwise the frame stays at A and the probe fails bydesign). Run:
pwsh scripts/run-m9.ps1(add-Broadcast+TWITCH_STREAM_KEYfor the optional 30s go-live).
Refs #53
🤖 Generated with Claude Code