wai: what nobody watches is ended with a receipt, and no bits above the panel (EN-B) #457

Closed
dcharlot wants to merge 0 commits from wai/energy-waste into main
Owner

EN-B of the WAI energy program: what nobody watches, and bits above the panel. A sink signs a log of each stream it receives, ends a video stream nobody can see with a signed termination, and records how every segment's rendition matched the panel it declared; a sender sends nothing above that panel. Based on origin/main at 41c5bafba; origin/main merged in twice since, last at b16816671 (#447), the final checks run on that merge.

What is in it

wai/extensions/stream-termination.md registers the record wai.live.termination: a sink's stream log, WSL1 records chained by digest, signed under a key the sink makes for its playback session (made as ad-presentation §3 makes one).

  • Open record (§3.2): the stream (a MoQ track by its track digest, an HTTP stream by its content description's URI), media, how it started (a user activation, or autoplay), the viewer's autoplay setting (a recorded setting) beside the platform's getAutoplayPolicy answer, the termination policy (unseen_grace_ms, idle_ms, autoplay_grace_ms), the display declaration and the ladder offered.
  • Segment receipt (§3.4): the segment's digest (SHA-256 of an HTTP segment, or over a MoQ segment's object content hashes), objects, bytes, media span, rendition, its match against the panel, and each hop's joules (origin, relay, sink decode, display) with its acquisition class. A hop the sink knows took place and did not measure is the unmetered marker; one it knows nothing of is absent. Never 0 J.
  • Observations (§3.6, §4.2): visibility (HTML §6.2's visibilityState), occlusion (the presenting element not intersecting the viewport, or the platform's window-occlusion report), display power (a native host's; a page has none), and interaction (HTML §6.4's activation-triggering input events, at most once a second, nothing of the input).
  • Display record (§3.3): the panel changed; applies from then.
  • Terminate record (§3.7): the reason (hidden, occluded, display_off, idle, autoplay_unattended, user, ended), the media time, and the hop joules up to termination, one total per (role, class), never across classes, plus unmetered counts per role.
  • Rules a verifier checks (§6.1), each with its refusal: reading (reason_unknown, energy_invalid, display_invalid, record_invalid, signature); nothing after a termination (after_termination: a stream terminated as hidden sends no further segment receipt); order and chain (order_invalid, sink_changed); a start by autoplay against the setting or a disallowed answer (autoplay_against_setting); segment media order (media_order_invalid); the rendition, its match and its admissibility (rendition_unknown, match_mismatch, rendition_above_panel); hidden/occluded/display-off for audio (reason_not_applicable: a hidden document still plays audio someone may hear); the reason against the state the log's own observations give at the termination, grace included (reason_unsupported); and the totals (totals_mismatch, a sum across two classes among them).
  • What an accepted log states (§6.3): totals and unmetered counts by role and class, absent roles, the panel counts, the unseen time, and the unwatched segments: those received after the sink's own grace had run out while nobody could see the stream.

wai/extensions/capability-constraints.md §4, display capability. The sink declares its panel: long/short (sides without orientation), refresh_milli, bit_depth, hdr, long_mm/short_mm, and §4.1 says how a page reads each (CSSOM View screen × devicePixelRatio, colorDepth, Media Queries 5 dynamic-range/video-dynamic-range, a refresh rate measured over 60 animation frames, physical size 0). A rendition's exceedances are four bits (resolution, refresh, bit depth, dynamic range); the admissible renditions are those within, or the least alone where none is (so a panel smaller than every rendition is still served); the match a receipt records is the exceedances plus none_within. A sender MUST NOT send, and a sink MUST NOT request, a rendition that is not admissible. The register gains wai:transfer (CICP TransferCharacteristics; 16 PQ and 18 HLG are HDR). Physical size is recorded and bounds nothing: what a viewer resolves depends on a distance the sink does not know.

Reference implementation.

  • wai-rs: module stream_termination (feature stream_termination, SHA-256 and Ed25519 only, builds for wasm32): the records, reading, the log verifier, the display match and a sink's Writer (which refuses to write after its termination). browser_sink::REGISTER follows §3.1 (with a test holding the two to each other). Registered: wai.live.termination (record) and the magic WSL1; the export regenerated. cbindgen skips the module (/// cbindgen:ignore), so include/wai.h is unchanged.
  • wai/wai-live-web: StreamLog (records written by the wai-rs writer), panelMatch, verifyStreamLog, terminationCorpusVerify, trackDigest; wai_live_web termination-verify <log.json> for the host.
  • Browser sink, joule-moq-web: web/room/termination.js (pure rules, Node-tested: the declaration, the refresh rate, which reason is due, segment gathering, activation events) and its use in web/room/app.js: a log per track received; the panel declared and re-declared when it changes; segment receipts every 2 s of verified objects, each hop the unmetered marker (a page has no meter; the room's object receipts carry the unmetered marker); a video stream hidden or out of view for unseen_grace_ms (default 10 s) is terminated, its subscription cancelled (MoqSession.unsubscribe: the request stream reset, moq-transport-22 §6.4.2.3), in-flight objects discarded unreceipted, the audio kept; seen again, a new log. autoplay=off (or the page's storage) holds media until the viewer's first activation.

Conformance. wai/live-conformance/termination/ (15 matches, 74 logs: every reason of §7 and every borrowed code refuses a log, each a one-defect control); stream_termination_verify.py, written from the two extensions' text, sharing no code with wai-rs, with a --log mode for one log a sink signed; a CI step in wai-conformance.yml; the e2e scenario termination in joule-moq-web.yml's run.

Design decisions, and why

  • A chained log, signed per record. Ordering and "nothing after the termination" become checkable from the log alone, and a segment receipt can be sent upstream as it is made. A resumed stream is a new log; a log is never resumed, which keeps after_termination absolute.
  • The verifier checks the reason against the log's own observations, with the grace the sink declared. It binds the sink's statements to one another, not to the screen; §9 says so.
  • Audio is never ended as unseen (reason_not_applicable), and the page keeps the audio when it ends the video.
  • Totals per (hop, class), never across: as live-metering §4.5, for the same reason.
  • Absent vs unmetered: the marker says the hop happened and was not measured; absence says nothing. The page states origin, sink decode and display as the marker and the relay as absent.
  • Least rendition when none is within, rather than refusing service; its receipt records none_within.
  • Orientation-free sides, so turning a device declares nothing new.
  • No consent gates (owner rule): §9 states what a log reveals and to whom, the fresh session key, and points to lawful-basis.
  • No figure profile for a hop figure in energy-measurement §6.1 in this revision, as live-metering's per-hop records have none; stated in §4.6.
  • The e2e page is driven over the DevTools protocol alone for the hidden step: the automation package enables focus emulation on every page it drives (it calls Emulation.setFocusEmulationEnabled), so a tab it drives never becomes hidden. Probed on the build host first: with the package, a background tab, a minimised window and a second target all stayed visible; over plain CDP, a tab put behind another reports hidden with a trusted visibilitychange, and visible again when brought back.

Checks (exact commands, results)

Heavy work ran on the build host through rb; light scripts locally. Head 2a99c78fd (the merge of b16816671) unless stated.

check command result
module unit tests cargo test --locked --no-default-features --features stream_termination --lib stream_termination:: 5 passed
corpus regenerates, every case, every reason, §7 and §4.2 tables cargo test --locked --no-default-features --features stream_termination --test stream_termination 5 passed
generator's verify wai_stream_termination_vectors verify ../live-conformance/termination both PASS
browser sink, after the register change cargo test --no-default-features --features browser_sink --lib browser_sink::; --features browser_sink,receipt_claims --test browser_sink (with a new test holding REGISTER to capability-constraints §3.1) 12 passed; 7 passed
registry cargo test --locked --lib codecs::registry (default features); wai registry export --check 14 passed; up to date
clippy, this slice's files cargo clippy --all-targets --no-default-features --features stream_termination, --features browser_sink,receipt_claims; wai-live-web cargo clippy --all-targets 0 notes in the slice's files (the crates' existing notes elsewhere unchanged)
wai-live-web cargo test --locked --lib; cargo build --release --lib --target wasm32-unknown-unknown 10 passed; builds
C header git diff --quiet include/wai.h after default and feature builds unchanged (/// cbindgen:ignore on the module)
independent verifier python3 -I live-conformance/stream_termination_verify.py live-conformance/termination every match and log agrees; 25 of 25 mutated copies fail; exit 0
verifiers reading shared text python3 -I live-conformance/browser_sink_verify.py live-conformance/browser pass; 28 of 28 mutated copies fail
page unit tests node --test web/room/termination.test.mjs (also in e2e/run.sh) 8 passed
browser e2e, termination e2e/run.sh --only termination on the build host, in its installed browser 16 of 16 checks ok; repeated --runs 3: 3 of 3 runs passed, 48 of 48 checks (hidden-to-terminated 3.3 s each time against a 3 s grace; 0 video objects after the cancel, 200 audio frames decoded)
browser e2e, every scenario e2e/run.sh (compose, webtransport, websocket, goaway, dvr, service, e2ee, age, co, binding, termination), at 56ef56f13; the later merge changed only a lockfile line identical to this branch's 1 of 1 runs passed, 247 of 247 checks ok, nothing skipped
lockfiles cargo metadata --locked --offline in wai-rs, wai-live-web, joule-code-rs, joule-moq-room all resolve
workflows ruby -ryaml on every workflow; wfsteps.py on each merge parse; 0 problems on both merges (on HEAD the one flagged step is this PR's new step, by construction)
repo checks check-tests-in-ci.py, check-workflow-concurrency.sh, check-spec-refs.py, check-wai-section-refs.py, check-no-vanity-metrics.sh, test-public-text-checks.sh, check-comparative-prose.sh, check-orphan-sources.py all exit 0
public names check-public-names.sh over the changed wai/ and joule-code/ files and the corpus; commit messages against the same patterns clean (34 files); no hit

Falsifications

Committed before each; each mutation's anchor matched exactly once, the crate compiled, and the named test failed; restored from git, tree clean after.

  • wai-rs, 37 of 37 caught (every_match_and_log_gets_its_stated_outcome fails): nothing after a termination; prev, seq, time order; sink change; autoplay setting; a display record in an audio log; segment overlap; rendition index; match recomputation; admissibility; a display record's declaration ignored; termination media; hidden for audio; the reason check; the grace; since restarting on a repeated observation; idle under a policy of 0; autoplay-unattended ignoring interactions and ignoring the start; totals unchecked; totals across classes; unmetered counts; energy classes; an unknown reason; declaration rules; record rules; orientation; each exceedance (refresh, depth, HLG as HDR); the least by index; none_within never set; trailing bytes; the signature; unseen time at the end; unwatched ignoring the grace. Two first survived (ignoring a display record's declaration; restarting the grace at a repeated observation): no log separated them. Logs were added (display records down and back up, a rendition kept above a shrunk panel, a repeated hidden observation), and both are now caught.
  • Python verifier, 16 of 16 caught with one check dropped at a time: after-termination, order, sink change, autoplay, media order, match, admissibility, not-applicable, unsupported, totals, energy, record rules, signature, grace, orientation, the least's tie-break.
  • The page's rules, 8 of 8 caught by termination.test.mjs: the grace, hidden applied to audio, since moved by a repeat, overlapping segments (first survived: a test was added for an object before the last segment's end with nothing gathered), untrusted events, CSS pixels declared unscaled, mean in place of median, autoplay-unattended after an interaction.
  • In the browser, 4 of 4 caught by the e2e scenario: the subscription not cancelled (60 video objects arrived in the 4 s after, against 0); a hidden video never terminated (the scenario times out waiting for the termination); a changed panel not declared in the log; the start stated as the user's.

Not done, and for the lead

  • Carriage of a log upstream. The page keeps its logs (roomTest.terminationLogs()); it does not send them to the room service, which has no endpoint for them. The spec leaves carriage to the deployment (§1).
  • Hop figures are all unmetered in the page. A page has no meter and the room's object receipts carry the unmetered marker, so the e2e logs show the marker for origin, sink decode and display. No per-segment measured figure exists yet; EN-A's sink metering is what would fill the sink-decode hop.
  • Aligning with EN-A. This record's hop roles (origin, relay, sink decode, display) are the per-hop split EN-A specifies. If EN-A defines its own codes or a display Estimator, the two should share one table; I did not reference EN-A's text, which is not on main.
  • No figure profile (energy-measurement §6.1) for a hop figure, so no measurement claim about one; stated in §4.6. live-metering's per-hop records have none either.
  • Display power in a page has no interface; only a native host observes it. There is no native host in this tree, so display_off is exercised by the corpus alone.
  • Physical size is declared (0 in a page) and bounds nothing.
  • Pre-existing, not touched: live-conformance/README.md repeats a sentence fragment three times (merge residue from earlier trains); browser_sink.rs's PENDING_BOUND and int_payload::unpinned_read warn as dead code in the wasm build; the name check flags the browser path in joule-moq-web.yml, a functional path outside the check's default scope; jouleclaw-loader-gguf's quick-xml warnings are left to the lead.
  • joule-moq-room's lock: this branch refreshed it (the same one-line change #447 made); after the merge it is identical to main's.

🤖 Generated with Claude Code


Closed: Landed on main through merge train E (#499, merge bf39ec9a); verified by content: this PR's head is in main

EN-B of the WAI energy program: what nobody watches, and bits above the panel. A sink signs a log of each stream it receives, ends a video stream nobody can see with a signed termination, and records how every segment's rendition matched the panel it declared; a sender sends nothing above that panel. Based on `origin/main` at `41c5bafba`; `origin/main` merged in twice since, last at `b16816671` (#447), the final checks run on that merge. ## What is in it **`wai/extensions/stream-termination.md`** registers the record `wai.live.termination`: a sink's **stream log**, `WSL1` records chained by digest, signed under a key the sink makes for its playback session (made as ad-presentation §3 makes one). - **Open record** (§3.2): the stream (a MoQ track by its track digest, an HTTP stream by its content description's URI), media, how it started (a user activation, or autoplay), the **viewer's autoplay setting** (a recorded setting) beside the platform's `getAutoplayPolicy` answer, the termination policy (`unseen_grace_ms`, `idle_ms`, `autoplay_grace_ms`), the display declaration and the ladder offered. - **Segment receipt** (§3.4): the segment's digest (SHA-256 of an HTTP segment, or over a MoQ segment's object content hashes), objects, bytes, media span, rendition, its **match** against the panel, and each hop's joules (origin, relay, sink decode, display) with its acquisition class. A hop the sink knows took place and did not measure is the unmetered marker; one it knows nothing of is absent. Never 0 J. - **Observations** (§3.6, §4.2): visibility (HTML §6.2's `visibilityState`), occlusion (the presenting element not intersecting the viewport, or the platform's window-occlusion report), display power (a native host's; a page has none), and interaction (HTML §6.4's activation-triggering input events, at most once a second, nothing of the input). - **Display record** (§3.3): the panel changed; applies from then. - **Terminate record** (§3.7): the reason (`hidden`, `occluded`, `display_off`, `idle`, `autoplay_unattended`, `user`, `ended`), the media time, and the hop joules up to termination, one total per (role, class), never across classes, plus unmetered counts per role. - **Rules a verifier checks** (§6.1), each with its refusal: reading (`reason_unknown`, `energy_invalid`, `display_invalid`, `record_invalid`, signature); nothing after a termination (`after_termination`: a stream terminated as hidden sends no further segment receipt); order and chain (`order_invalid`, `sink_changed`); a start by autoplay against the setting or a `disallowed` answer (`autoplay_against_setting`); segment media order (`media_order_invalid`); the rendition, its match and its admissibility (`rendition_unknown`, `match_mismatch`, `rendition_above_panel`); hidden/occluded/display-off for audio (`reason_not_applicable`: a hidden document still plays audio someone may hear); the reason against the state the log's own observations give at the termination, grace included (`reason_unsupported`); and the totals (`totals_mismatch`, a sum across two classes among them). - **What an accepted log states** (§6.3): totals and unmetered counts by role and class, absent roles, the panel counts, the unseen time, and the **unwatched segments**: those received after the sink's own grace had run out while nobody could see the stream. **`wai/extensions/capability-constraints.md` §4, display capability.** The sink declares its panel: `long`/`short` (sides without orientation), `refresh_milli`, `bit_depth`, `hdr`, `long_mm`/`short_mm`, and §4.1 says how a page reads each (CSSOM View `screen` × `devicePixelRatio`, `colorDepth`, Media Queries 5 `dynamic-range`/`video-dynamic-range`, a refresh rate measured over 60 animation frames, physical size 0). A rendition's **exceedances** are four bits (resolution, refresh, bit depth, dynamic range); the **admissible** renditions are those within, or the **least** alone where none is (so a panel smaller than every rendition is still served); the **match** a receipt records is the exceedances plus `none_within`. A sender MUST NOT send, and a sink MUST NOT request, a rendition that is not admissible. The register gains `wai:transfer` (CICP `TransferCharacteristics`; 16 PQ and 18 HLG are HDR). Physical size is recorded and bounds nothing: what a viewer resolves depends on a distance the sink does not know. **Reference implementation.** - `wai-rs`: module `stream_termination` (feature `stream_termination`, SHA-256 and Ed25519 only, builds for wasm32): the records, reading, the log verifier, the display match and a sink's `Writer` (which refuses to write after its termination). `browser_sink::REGISTER` follows §3.1 (with a test holding the two to each other). Registered: `wai.live.termination` (record) and the magic `WSL1`; the export regenerated. cbindgen skips the module (`/// cbindgen:ignore`), so `include/wai.h` is unchanged. - `wai/wai-live-web`: `StreamLog` (records written by the `wai-rs` writer), `panelMatch`, `verifyStreamLog`, `terminationCorpusVerify`, `trackDigest`; `wai_live_web termination-verify <log.json>` for the host. - Browser sink, `joule-moq-web`: `web/room/termination.js` (pure rules, Node-tested: the declaration, the refresh rate, which reason is due, segment gathering, activation events) and its use in `web/room/app.js`: a log per track received; the panel declared and re-declared when it changes; segment receipts every 2 s of verified objects, each hop the unmetered marker (a page has no meter; the room's object receipts carry the unmetered marker); a video stream hidden or out of view for `unseen_grace_ms` (default 10 s) is terminated, its subscription cancelled (`MoqSession.unsubscribe`: the request stream reset, moq-transport-22 §6.4.2.3), in-flight objects discarded unreceipted, the audio kept; seen again, a new log. `autoplay=off` (or the page's storage) holds media until the viewer's first activation. **Conformance.** `wai/live-conformance/termination/` (15 matches, 74 logs: every reason of §7 and every borrowed code refuses a log, each a one-defect control); `stream_termination_verify.py`, written from the two extensions' text, sharing no code with `wai-rs`, with a `--log` mode for one log a sink signed; a CI step in `wai-conformance.yml`; the e2e scenario `termination` in `joule-moq-web.yml`'s run. ## Design decisions, and why - **A chained log, signed per record.** Ordering and "nothing after the termination" become checkable from the log alone, and a segment receipt can be sent upstream as it is made. A resumed stream is a new log; a log is never resumed, which keeps `after_termination` absolute. - **The verifier checks the reason against the log's own observations**, with the grace the sink declared. It binds the sink's statements to one another, not to the screen; §9 says so. - **Audio is never ended as unseen** (`reason_not_applicable`), and the page keeps the audio when it ends the video. - **Totals per (hop, class), never across**: as live-metering §4.5, for the same reason. - **Absent vs unmetered**: the marker says the hop happened and was not measured; absence says nothing. The page states origin, sink decode and display as the marker and the relay as absent. - **Least rendition when none is within**, rather than refusing service; its receipt records `none_within`. - **Orientation-free sides**, so turning a device declares nothing new. - **No consent gates** (owner rule): §9 states what a log reveals and to whom, the fresh session key, and points to `lawful-basis`. - **No figure profile for a hop figure** in energy-measurement §6.1 in this revision, as live-metering's per-hop records have none; stated in §4.6. - **The e2e page is driven over the DevTools protocol alone** for the hidden step: the automation package enables focus emulation on every page it drives (it calls `Emulation.setFocusEmulationEnabled`), so a tab it drives never becomes hidden. Probed on the build host first: with the package, a background tab, a minimised window and a second target all stayed `visible`; over plain CDP, a tab put behind another reports `hidden` with a trusted `visibilitychange`, and `visible` again when brought back. ## Checks (exact commands, results) Heavy work ran on the build host through `rb`; light scripts locally. Head `2a99c78fd` (the merge of `b16816671`) unless stated. | check | command | result | |---|---|---| | module unit tests | `cargo test --locked --no-default-features --features stream_termination --lib stream_termination::` | 5 passed | | corpus regenerates, every case, every reason, §7 and §4.2 tables | `cargo test --locked --no-default-features --features stream_termination --test stream_termination` | 5 passed | | generator's verify | `wai_stream_termination_vectors verify ../live-conformance/termination` | both PASS | | browser sink, after the register change | `cargo test --no-default-features --features browser_sink --lib browser_sink::`; `--features browser_sink,receipt_claims --test browser_sink` (with a new test holding `REGISTER` to capability-constraints §3.1) | 12 passed; 7 passed | | registry | `cargo test --locked --lib codecs::registry` (default features); `wai registry export --check` | 14 passed; up to date | | clippy, this slice's files | `cargo clippy --all-targets --no-default-features --features stream_termination`, `--features browser_sink,receipt_claims`; `wai-live-web` `cargo clippy --all-targets` | 0 notes in the slice's files (the crates' existing notes elsewhere unchanged) | | wai-live-web | `cargo test --locked --lib`; `cargo build --release --lib --target wasm32-unknown-unknown` | 10 passed; builds | | C header | `git diff --quiet include/wai.h` after default and feature builds | unchanged (`/// cbindgen:ignore` on the module) | | independent verifier | `python3 -I live-conformance/stream_termination_verify.py live-conformance/termination` | every match and log agrees; 25 of 25 mutated copies fail; exit 0 | | verifiers reading shared text | `python3 -I live-conformance/browser_sink_verify.py live-conformance/browser` | pass; 28 of 28 mutated copies fail | | page unit tests | `node --test web/room/termination.test.mjs` (also in `e2e/run.sh`) | 8 passed | | browser e2e, termination | `e2e/run.sh --only termination` on the build host, in its installed browser | 16 of 16 checks ok; repeated `--runs 3`: 3 of 3 runs passed, 48 of 48 checks (hidden-to-terminated 3.3 s each time against a 3 s grace; 0 video objects after the cancel, 200 audio frames decoded) | | browser e2e, every scenario | `e2e/run.sh` (compose, webtransport, websocket, goaway, dvr, service, e2ee, age, co, binding, termination), at `56ef56f13`; the later merge changed only a lockfile line identical to this branch's | 1 of 1 runs passed, 247 of 247 checks ok, nothing skipped | | lockfiles | `cargo metadata --locked --offline` in wai-rs, wai-live-web, joule-code-rs, joule-moq-room | all resolve | | workflows | `ruby -ryaml` on every workflow; `wfsteps.py` on each merge | parse; 0 problems on both merges (on HEAD the one flagged step is this PR's new step, by construction) | | repo checks | `check-tests-in-ci.py`, `check-workflow-concurrency.sh`, `check-spec-refs.py`, `check-wai-section-refs.py`, `check-no-vanity-metrics.sh`, `test-public-text-checks.sh`, `check-comparative-prose.sh`, `check-orphan-sources.py` | all exit 0 | | public names | `check-public-names.sh` over the changed `wai/` and `joule-code/` files and the corpus; commit messages against the same patterns | clean (34 files); no hit | ## Falsifications Committed before each; each mutation's anchor matched exactly once, the crate compiled, and the named test failed; restored from git, tree clean after. - **wai-rs, 37 of 37 caught** (`every_match_and_log_gets_its_stated_outcome` fails): nothing after a termination; prev, seq, time order; sink change; autoplay setting; a display record in an audio log; segment overlap; rendition index; match recomputation; admissibility; a display record's declaration ignored; termination media; hidden for audio; the reason check; the grace; `since` restarting on a repeated observation; idle under a policy of 0; autoplay-unattended ignoring interactions and ignoring the start; totals unchecked; totals across classes; unmetered counts; energy classes; an unknown reason; declaration rules; record rules; orientation; each exceedance (refresh, depth, HLG as HDR); the least by index; `none_within` never set; trailing bytes; the signature; unseen time at the end; unwatched ignoring the grace. **Two first survived** (ignoring a display record's declaration; restarting the grace at a repeated observation): no log separated them. Logs were added (display records down and back up, a rendition kept above a shrunk panel, a repeated hidden observation), and both are now caught. - **Python verifier, 16 of 16 caught** with one check dropped at a time: after-termination, order, sink change, autoplay, media order, match, admissibility, not-applicable, unsupported, totals, energy, record rules, signature, grace, orientation, the least's tie-break. - **The page's rules, 8 of 8 caught** by `termination.test.mjs`: the grace, hidden applied to audio, `since` moved by a repeat, overlapping segments (first survived: a test was added for an object before the last segment's end with nothing gathered), untrusted events, CSS pixels declared unscaled, mean in place of median, autoplay-unattended after an interaction. - **In the browser, 4 of 4 caught** by the e2e scenario: the subscription not cancelled (60 video objects arrived in the 4 s after, against 0); a hidden video never terminated (the scenario times out waiting for the termination); a changed panel not declared in the log; the start stated as the user's. ## Not done, and for the lead - **Carriage of a log upstream.** The page keeps its logs (`roomTest.terminationLogs()`); it does not send them to the room service, which has no endpoint for them. The spec leaves carriage to the deployment (§1). - **Hop figures are all unmetered in the page.** A page has no meter and the room's object receipts carry the unmetered marker, so the e2e logs show the marker for origin, sink decode and display. No per-segment measured figure exists yet; EN-A's sink metering is what would fill the sink-decode hop. - **Aligning with EN-A.** This record's hop roles (origin, relay, sink decode, display) are the per-hop split EN-A specifies. If EN-A defines its own codes or a display Estimator, the two should share one table; I did not reference EN-A's text, which is not on main. - **No figure profile** (energy-measurement §6.1) for a hop figure, so no `measurement` claim about one; stated in §4.6. live-metering's per-hop records have none either. - **Display power in a page** has no interface; only a native host observes it. There is no native host in this tree, so `display_off` is exercised by the corpus alone. - **Physical size** is declared (0 in a page) and bounds nothing. - **Pre-existing, not touched:** `live-conformance/README.md` repeats a sentence fragment three times (merge residue from earlier trains); `browser_sink.rs`'s `PENDING_BOUND` and `int_payload::unpinned_read` warn as dead code in the wasm build; the name check flags the browser path in `joule-moq-web.yml`, a functional path outside the check's default scope; `jouleclaw-loader-gguf`'s quick-xml warnings are left to the lead. - **joule-moq-room's lock**: this branch refreshed it (the same one-line change #447 made); after the merge it is identical to main's. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- **Closed:** Landed on main through merge train E (#499, merge bf39ec9a); verified by content: this PR's head is in main
stream-termination.md registers wai.live.termination: a sink's signed
stream log (WSL1) with an open record (autoplay setting, termination
policy, display declaration, ladder), a receipt per segment (the
rendition's match against the panel, each hop's joules with its class),
observations (visibility, occlusion, display power, interaction) and a
termination with its reason, media time and totals per hop and class.
capability-constraints §4 adds the display declaration, the match and
the admissible renditions, and wai:transfer to the register.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
stream_termination_verify.py is the corpus's second implementation,
written from the two extensions' text; it agrees with wai-rs on every
match and log and requires every mutated copy to fail. The browser
sink's register follows capability-constraints §3.1, and a test holds
the two to each other.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-live-web gains StreamLog (records written by wai-rs's writer), the
panel match, the log verifier and the corpus check for a page, and
wai_live_web termination-verify for the host. The room page keeps a
stream log for each track it receives (web/room/termination.js): the
panel it declares, segment receipts with the match, the observations of
visibility, occlusion and interaction, and a signed termination when a
video stream has been unseen for its grace; the subscription is then
ended and the audio kept, and a new log opens when the page is seen.
The e2e scenario drives the receiving page over the DevTools protocol
so its tab really goes to the background.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The lock still named 0.6.12 after wai-quantum moved to 0.6.14, so every
--locked build of the room service refused to start (its CI job, and
joule-moq-web's end-to-end run, which builds it). Resolve-only refresh.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two mutations of the reference survived the corpus: ignoring a display
record's declaration, and restarting a signal's grace at a repeated
observation. Each now changes a log's outcome.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
cbindgen skips the module, whose record constants are not part of the C
surface, so include/wai.h is unchanged by a build.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merge origin/main (b1681667) into wai/energy-waste
Some checks failed
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Has been cancelled
WAI energy receipts / does this change touch it (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / does this change touch it (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
standards workspaces build + test / which workspaces this change touches (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (pull_request) Has been cancelled
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Has been cancelled
2a99c78fd5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dcharlot closed this pull request 2026-10-26 20:58:03 -04:00
Some checks failed
wai-web wasm32 / does this change touch it (pull_request) Has been cancelled
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Has been cancelled
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Has been cancelled
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Has been cancelled
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Has been cancelled
wai-quantum studio and desktop / does this change touch it (pull_request) Has been cancelled
wai-quantum studio and desktop / studio · browser (pull_request) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (pull_request) Has been cancelled
wai-pack / wai-pack · does this change touch it (pull_request) Has been cancelled
wai-pack / wai-pack · linux (pull_request) Has been cancelled
wai-pack / wai-pack · linux-arm64 (pull_request) Has been cancelled
wai-pack / wai-pack · macos (pull_request) Has been cancelled
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been cancelled
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been cancelled
wai-pack / wai-pack · dependency licences (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Has been cancelled
WAI energy receipts / does this change touch it (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / does this change touch it (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
standards workspaces build + test / which workspaces this change touches (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (pull_request) Has been cancelled
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Has been cancelled

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!457
No description provided.