open-standards/wai/extensions/stream-termination.md
Transaction Science f7d55b28a5 wai: the refresh rate a page measures, stated exactly; wording
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-22 13:53:23 -04:00

34 KiB
Raw Permalink Blame History

WAI Extension: Stream Termination

Status: Draft. Registers the record wai.live.termination: a sink's signed account of one stream it received, from its start to the moment it stopped, and why it stopped. The sink signs a stream log: a chain of records, WSL1, each naming the one before it by digest, under a key it makes for its playback session. The log opens with the stream, how it started (by a user's activation, or by autoplay, with the viewer's autoplay setting recorded beside the platform's answer), the sink's termination policy and its display declaration (capability-constraints §4); it then holds a receipt for each segment the sink received, with the rendition and how it matched the panel and each hop's joules with their acquisition class (energy-measurement §2); the observations of whether anyone could be watching (the document's visibility, occlusion where the platform reports it, the display's power and the viewer's interaction); and it ends with a termination: the reason (hidden, occluded, display off, idle, autoplayed with no interaction, stopped by the viewer, or ended), the media time, and the joules of each hop up to that point, a total per hop and class and never one across classes. A verifier checks the chain, that nothing follows the termination, that the reason is one the log's own observations support, that every segment's rendition was admissible under the panel, and the totals; it reports how much was delivered while nobody could see it. No new transport. The reference implementation is the stream_termination module of wai-rs (feature stream_termination), which wai/wai-live-web builds for a page; the page's own code is joule-code/joule-code-rs/crates/joule-moq-web/web/room/termination.js; the corpus is live-conformance/termination/, and its second implementation is live-conformance/stream_termination_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Texts read for this revision, each pinned to the version named: the HTML Standard, Living Standard last updated 20 October 2026, §6.2 (page visibility: a document's visibility state, "update the visibility state" and the visibilitychange event) and §6.4 (user activation: the activation-triggering input events and the UserActivation interface); Page Visibility Level 2, W3C Discontinued Draft 23 June 2022, which defers to the HTML Standard and was read for its note on when a document is hidden; Autoplay Policy Detection, W3C Working Draft 4 September 2025, §2.1 (getAutoplayPolicy() and its three values); Intersection Observer, W3C Working Draft 18 October 2023, §2.3 (isIntersecting); and the texts capability-constraints §4.1 names. A section number in this text is this extension's unless it names another document.

1. Scope

In scope: the stream log, its records, their signature and their rules (§3); what a sink observes, when it terminates a stream, the autoplay setting, the segments it receipts and the hop figures it states (§4); what a sender does with a termination and a display declaration (§5); verification, the state a log shows at each instant and what an accepted log states (§6); and the reasons (§7).

Out of scope: a transport, and how a log reaches a sender or an operator, which a deployment carries as it carries its other receipts; how a meter measures, which energy-measurement governs; a figure for the display's own energy, which this revision does not compute; and attention, which nothing here measures: a stream nobody can see is in scope, one nobody looks at is not.

Relationship to the core spec: wai.live.termination is a record (SPEC §5): a signed record a WAI tool emits and verifies. It names no payload format and is not dispatchable.

Relationship to the other extensions:

  • capability-constraints §4. The display declaration, the match and the admissible renditions. A segment receipt records the match (§3.4), and §6.1 step 6 refuses a segment of a rendition that was not admissible.
  • energy-measurement §2. Each hop figure's class, its tag and its evidence (§2.7), and the unmetered marker, which is never a figure of zero joules.
  • live-metering. A hop's own record of a room session is per session and per hop; a stream log is per stream at one sink. The two are separate records, and neither restates the other.
  • ad-presentation §3, §10. The session key is made as that extension's is, and a sink that measures a fraction in view for an ad claim reads the same observations this extension records.
  • delivery-telemetry. Its observations are unsigned and aggregated; a stream log is signed and is one stream's.

2. Terms

  • Sink: the software that receives a stream and presents it: a page, or a native host.
  • Stream: one sink's reception of one media track: a MoQ subscription to a track, or an HTTP client's requests for the segments of one media type of a presentation. Its media is video or audio.
  • Segment: what a sink receipts as one unit of a stream: one HTTP segment, or the objects of a MoQ track the sink received over a span of media time it chooses (§4.5).
  • Session key: an Ed25519 key (RFC 8032) the sink makes for one playback session (§4.1).
  • Unseen: a video stream is unseen while its document is hidden, its presentation area is occluded or its display is off (§4.2).
  • Hop: a party whose energy a segment's delivery and presentation cost, by its role: 1 origin, 2 relay, 3 sink decode, 4 display.
  • Unmetered marker: a zero figure with class tag 0 (energy-measurement §2), never a figure of zero joules. A hop the sink states nothing about is absent, which is not the marker either: the marker says the hop took place and was not measured, and absence says nothing.
  • u64, u32, u8: little-endian unsigned integers of that many bits; the class's evidence is big-endian, as energy-measurement §2.7 writes it. A digest is SHA-256 (FIPS 180-4), 32 bytes. Times t are microseconds since 1970-01-01T00:00:00Z on the sink's clock; media times are microseconds of the stream's media timeline.

3. The stream log

3.1 Records

A stream log is a sequence of records, each:

field size meaning
magic 4 WSL1 (ASCII)
version 1 1 in this revision
kind 1 1 open, 2 segment, 3 observation, 4 display, 5 terminate
sink 32 the session key, which signs the record
seq 4 u32: the record's index in the log, from 0
prev 32 the digest of the record before it; 32 zero bytes for the first
t 8 u64: when the sink made the record
body … the kind's body (§3.2 to §3.7)
sig 64 §3.8

3.2 The open record

The first record of a log, and the only one of kind 1:

field size meaning
stream 32 the stream: for a MoQ track, its track digest (live-metering §2: SHA-256 over the namespace as live-rooms §6.2 renders it, one zero byte, and the name); for an HTTP stream, SHA-256 over the URI of the content description as the sink requested it
media 1 1 video, 2 audio
start 1 1: a user activation started it; 2: it started with none (autoplay)
autoplay 1 the viewer's autoplay setting at the sink: 1 on, 2 off (§4.4)
ua_policy 1 the platform's answer for autoplay (§4.4): 0 not asked, 1 allowed, 2 allowed-muted, 3 disallowed
unseen_grace_ms 4 u32: how long a video stream stays unseen before the sink terminates it (§4.3)
idle_ms 4 u32: how long with no interaction before it terminates the stream as idle; 0 for never
autoplay_grace_ms 4 u32: how long an autoplayed stream with no interaction runs before it terminates it; 0 for never
display 22 the display declaration (§3.3)
ladder 1 + 14 × n a u8 count n, then each rendition the sender offers: width, height and frame_rate_milli (u32 each), bit_depth (u8) and transfer (u8), the parameters of capability-constraints §3.1

A video stream has a ladder of 1 to 32 renditions and a display declaration; an audio stream has a ladder of none, and its declaration is 22 zero bytes.

3.3 The display declaration

capability-constraints §4.1's members, 22 bytes:

field size meaning
long 4 u32
short 4 u32
refresh_milli 4 u32
bit_depth 1 u8
hdr 1 0 false, 1 true
long_mm 4 u32; 0 not known
short_mm 4 u32; 0 not known

It is the body of a display record (kind 4) as well, which a sink signs when its panel changes: the declaration in effect at a record is the one in the last display record before it, or the open record's where there is none.

3.4 The segment record

A segment receipt (kind 2):

field size meaning
segment 32 the segment: for an HTTP segment, SHA-256 over its bytes as received; for a MoQ segment, SHA-256 over the content hashes (jwp-receipts §2.1) of its objects in the order received, concatenated
objects 4 u32: the objects it holds, at least 1; 1 for an HTTP segment
bytes 8 u64: their payload bytes as received
media_start 8 u64: the media time of its first sample
media_end 8 u64: the media time its last sample ends at, above media_start
rendition 1 for video, its rendition's index in the open record's ladder; for audio, 255
match 1 for video, the match of that rendition (capability-constraints §4.2) against the declaration in effect: bits 0 to 3 its exceedances, bit 4 none_within, every other bit 0; for audio, 0
hops 1 + … a u8 count, at most 4, then each hop entry (§3.5) in strictly ascending order of role

3.5 Hop entries

field size meaning
role 1 1 origin, 2 relay, 3 sink decode, 4 display
joules 8 u64: the hop's energy for this segment, in microjoules; 0 is the unmetered marker
class 1 + … its acquisition class: energy-measurement §2.7's tag byte, then that tag's evidence

A hop with no entry is absent (§2).

3.6 The observation record

field size meaning
signal 1 1 visibility, 2 occlusion, 3 display power, 4 interaction
value 1 visibility: 1 visible, 2 hidden; occlusion: 1 not occluded, 2 occluded; display power: 1 on, 2 off; interaction: 1

What each signal is, and where a sink reads it, is §4.2.

3.7 The terminate record

field size meaning
reason 1 1 hidden, 2 occluded, 3 display_off, 4 idle, 5 autoplay_unattended, 6 user, 7 ended (§4.3)
media 8 u64: the media time at which the sink stopped presenting the stream
segments 4 u32: the segment receipts of the log
totals 1 + 14 × m a u8 count m, then for each hop role and class among the segments' metered hop entries: role (u8), tag (u8), figures (u32, the entries of that role and tag) and joules (u64, their sum), in strictly ascending order of (role, tag)
unmetered 1 + 5 × k a u8 count k, then for each role with entries that are the unmetered marker: role (u8) and count (u32), in strictly ascending order of role

The totals are the joules of each hop up to the termination: one per role and class, never one across two classes or two roles (live-metering §4.5 gives the reason). A role in neither list is absent from every segment, and a verifier names it so (§6.3).

3.8 Digest and signature

The digest of a record is SHA-256 over its bytes before sig. Its signature is Ed25519 by sink over the 15 bytes wai:stream-log (ASCII) and 0x01, followed by the bytes the digest covers, verified strictly, as SPEC §7 Signatures requires: a key that fails step 1 or 2 is bad_key, and a signature that fails a later step is bad_signature.

3.9 Reading

A reader reads exactly the bytes it is given as one record, as live-events §3.3 reads a record with WSL1 for WEV1: fewer than 4 bytes, or a magic of WSL1 with fewer than 5, is truncated; another magic is malformed; a version other than 1 is unknown_version; a kind other than 1 to 5 is malformed; running out of bytes before sig ends is truncated, and bytes after it are malformed. The class's evidence is read as energy-measurement §2.7 writes it, and a tag other than 4 and 5 is followed by nothing; for tag 5 a reference length above 255 is malformed.

It then refuses, in this order:

  1. as reason_unknown, a terminate record whose reason is not 1 to 7;
  2. as energy_invalid, a hop entry whose class breaks live-metering §3.6's energy_invalid rules over its joules: the tag is 0 exactly when joules is 0; the tag is 0, 2, 3, 4 or 5, never the legacy 1 (energy-measurement §2.6); for tags 4 and 5, relative_ppm and window_us are above 0 and joules × relative_ppm ≥ 288 676 (energy-measurement §2.3), tag 4's filtering byte is 0, 1 or 2, and tag 5's reference is UTF-8 and not only white space;
  3. as display_invalid, an open record of video or a display record whose declaration breaks capability-constraints §4.1: short from 1 to long, refresh_milli at least 1, bit_depth 1 to 16, hdr 0 or 1; and an open record of audio whose declaration is not 22 zero bytes;
  4. as record_invalid, a record that breaks any of these:
    • open: media, start and autoplay are 1 or 2, ua_policy 0 to 3; a video stream's ladder holds 1 to 32 renditions, each with width, height and frame_rate_milli at least 1 and bit_depth 1 to 16; an audio stream's holds none;
    • segment: objects at least 1; media_start below media_end; no bit of match above bit 4; at most 4 hop entries, each role 1 to 4, in strictly ascending order;
    • observation: signal 1 to 4; value 1 or 2 for signals 1 to 3, and 1 for signal 4;
    • terminate: each total's role 1 to 4, tag 2 to 5 and figures at least 1, in the order §3.7 gives; each unmetered entry's role 1 to 4 and count at least 1, in its order.

and then checks the signature (§3.8).

4. What a sink records

4.1 The session key

A sink MUST make a fresh Ed25519 key for each playback session, from its platform's random generator, and MUST NOT derive it from an identifier of the viewer, the device or an account; it signs every log of that session with it. It presents the key's public part to the senders of its streams with its first request, by a means this revision does not fix. It MAY make a key per stream instead, so that no two of its logs link.

4.2 The attention state

A sink records an observation when a signal changes, at the time it learns of the change, and one for each signal whose value at the open record is not the initial value below, with the open record's t. Before any observation, a stream is visible, not occluded, its display on, and it has seen no interaction.

signal what it is where a page reads it where a native host reads it
1 visibility whether the document, or the application, is shown at all the document's visibility state, document.visibilityState, on each visibilitychange event (HTML §6.2) the platform's state of its window or application: minimised, in the background, behind the lock screen
2 occlusion whether the area the video is presented in can be seen, while the document is visible the element that presents the video does not intersect the viewport: an IntersectionObserver on it reports isIntersecting false (Intersection Observer §2.3) the platform's report that the window is fully covered, where it makes one
3 display power whether the display is on a page has no interface for it; a display that goes off makes a document hidden on the platforms the HTML Standard's note describes, which the page records as visibility the platform's notice that the display slept or woke
4 interaction that the viewer interacted an activation-triggering input event (HTML §6.4: a trusted keydown, mousedown, pointerdown, pointerup or touchend of the kinds that section lists) the platform's input to the application

A sink records at most one interaction observation per second: the rules of §4.3 read only the time of the last one, and a finer record says more about the viewer than they need. An interaction observation carries nothing of the input.

The HTML Standard has a document hidden when its window is minimised, its tab is in the background, or a system element such as a task switcher covers it; Page Visibility Level 2's note adds the lock screen. Neither requires a user agent to report a window that other windows cover, and Page Visibility Level 2 allows one to report it visible. A page records occlusion only for what it can see through Intersection Observer, which is the element's place against the viewport and not what other content covers (its §1 sets pixel-accurate visibility outside its goals). A sink records what its platform reports and claims nothing it does not.

4.3 Termination

A sink that terminates a stream signs a terminate record, with the reason, and then:

  • MUST end its request for the stream: unsubscribe from the MoQ track, or request no further segment of it;
  • MUST NOT sign any record of the log after the terminate record. A segment that arrives after it, already in flight, is discarded and not receipted. A stream terminated as hidden sends no further segment receipt;
  • MAY open a new log for the same stream when the condition ends (the document visible again, an interaction), with a new open record; a log is never resumed.

The reasons, each with the state the log must show at the terminate record (§6.2):

reason when
1 hidden a video stream whose document has been hidden for at least unseen_grace_ms
2 occluded a video stream whose presentation area has been occluded for at least unseen_grace_ms
3 display_off a video stream whose display has been off for at least unseen_grace_ms
4 idle idle_ms is above 0, and the last interaction, or the open record where there was none, is at least idle_ms before
5 autoplay_unattended a stream that started by autoplay, with autoplay_grace_ms above 0, no interaction since it opened, and open for at least autoplay_grace_ms
6 user the viewer stopped it
7 ended anything else: the stream ended, the session closed, the sink moved to another point of the stream or to another stream

A sink SHOULD terminate a video stream that has been unseen for unseen_grace_ms, and a stream that meets its own idle or autoplay rule, and MUST state as the reason one that the log supports. Hidden, occluded and display-off apply to video alone: a hidden document still plays audio someone may hear, so a sink that terminates the video of a presentation as hidden keeps its audio, whose own log goes on. The grace periods and timeouts are the deployment's choice; a value of the open record is the sink's statement of the policy it applied.

4.4 The autoplay setting

autoplay is the viewer's setting at the sink, which a service offers its viewers (a player's "autoplay off"). ua_policy is the user agent's answer to navigator.getAutoplayPolicy for media elements (Autoplay Policy Detection §2.1), or 0 where the sink did not ask or has no such interface. A sink MUST NOT start a stream by autoplay when its viewer's setting is off or the platform answered disallowed; a log that states such a start is refused autoplay_against_setting.

4.5 Segments and the panel

A sink signs a segment receipt for each segment it received and verified, as it receives it. For a MoQ track it chooses the span: the objects of one group, or of a span of media time at most 10 seconds long, ending at a group boundary. It states the rendition the segment is of, and the match of that rendition against the declaration in effect (capability-constraints §4.2), computed over the open record's ladder.

The ladder is what the sender offers: an HLS multivariant playlist's variant streams with their RESOLUTION, FRAME-RATE and VIDEO-RANGE (SDR as transfer 1, HLG as 18, PQ as 16; rfc8216bis-22 §4.4.6.2), a DASH MPD's video representations, or the video tracks of a MoQ catalog with their width, height and framerate. Where the content description does not state a parameter, the sink states what it reads from the decoder's configuration: the coded size, the bit depth of the codec string's profile, and transfer 1 where the stream carries no colour description.

4.6 Hop figures

A segment receipt states, for each hop the sink holds a figure for, that figure with its class (energy-measurement §2):

  • origin: the figure the segment's object receipts carry in their labelled leaves (jwp-receipts §2.2), composed over the segment's objects as jwp-receipts §2.4.1 composes a group total; the unmetered marker where every object's figure is the marker; absent where a figure is unlabelled, since an unlabelled figure has no class;
  • relay: a figure a relay states for the segment, where its deployment gives the sink one; this revision defines no such statement, so a relay hop is usually absent;
  • sink decode: the sink's own figure for decoding the segment, measured as energy-measurement §3 and §4 have it, or the unmetered marker where the sink decoded it with no meter;
  • display: a figure the sink's platform states for presenting the segment, or the unmetered marker. This revision computes none.

A hop the sink knows took place but did not measure is the unmetered marker; it MUST NOT be written as a figure of zero joules, and a reader MUST NOT total it as one.

A hop figure is a figure energy-measurement governs (its §1): its class is inside the record's signature with its evidence, and §3.9 step 2 holds it to that extension's rules. This revision names no figure profile for it (energy-measurement §6.1), so no measurement claim can be made about one, as about a figure of live-metering's per-hop records, which have none either. A sink that measured a figure keeps its bracket as energy-measurement §3 and §6 ask, and a revision that adds the profile adds its row to that table.

5. What a sender does

  • A sender that holds a sink's display declaration MUST NOT send it a rendition that is not admissible (capability-constraints §4.2).
  • A sender that receives a terminate record, or the end of the request that goes with it (§4.3), MUST stop sending the stream to that sink; it MAY keep sending the same presentation's other streams, its audio among them.
  • A sender MAY verify a log it is given (§6) and keep it with its own records of the session. A log is the sink's statement: its observations and figures rest on the sink's key (§9).

6. Verification

6.1 Steps

A verifier is given the records of one log, in order. For each record, in order, it runs these steps and stops at the first refusal, which it reports with the reason and the record's index:

  1. Reading (§3.9): truncated, malformed, unknown_version, reason_unknown, energy_invalid, display_invalid, record_invalid, bad_key, bad_signature.
  2. After the termination: a record after a terminate record is after_termination.
  3. Order: the first record is an open record and no other is; each seq is the record's index; each prev is the digest of the record before it, and 32 zero bytes for the first; each t is at least the t before it. Otherwise order_invalid; a log of no record is refused order_invalid at index 0. Each record's sink is the first's: otherwise sink_changed.
  4. The open record: start is 2 while autoplay is 2 or ua_policy is 3: autoplay_against_setting.
  5. A display record in the log of an audio stream: display_invalid.
  6. A segment receipt:
    1. its media_start is at least the media_end of the segment receipt before it, where there is one: otherwise media_order_invalid;
    2. for video, its rendition is below the ladder's count, and for audio it is 255: otherwise rendition_unknown;
    3. for video, its match is the match capability-constraints §4.2 gives its rendition against the declaration in effect over the ladder, and for audio it is 0: otherwise match_mismatch;
    4. for video, its rendition is admissible: otherwise rendition_above_panel.
  7. The terminate record:
    1. where a segment receipt precedes it, its media lies from the last one's media_start to its media_end, both included: otherwise media_order_invalid;
    2. its reason is hidden, occluded or display_off in the log of an audio stream: reason_not_applicable;
    3. the state of §6.2 at its t does not support its reason by the table of §4.3: reason_unsupported;
    4. its segments, totals and unmetered are not those the segment receipts before it give (§3.7): totals_mismatch.

A log that passes is accepted. One with no terminate record is accepted as open: the stream is still running, or its sink stopped without saying why.

6.2 The state at an instant

The state of a log at a record is read from the observation records before it. For each of visibility, occlusion and display power it is the value of the last observation of that signal, or the initial value of §4.2, and its since is the t of the earliest observation of the run of observations of that signal, ending with the last, that all state the same value, or the open record's t where there is none. The last interaction is the t of the last interaction observation, or none.

A video stream is unseen at a record when visibility is hidden, occlusion occluded or display power off there. Reading the observation records in order from the initial state, its unseen since is the t of the observation after which it became unseen, from a state in which it was not, and has stayed unseen at every observation since.

At a terminate record at time T, with the open record's t written T0:

  • hidden is supported when visibility is hidden and T − since ≥ unseen_grace_ms × 1000;
  • occluded when occlusion is occluded, and display_off when display power is off, each with the same bound;
  • idle when idle_ms is above 0 and T − (the last interaction, or T0 where there is none) ≥ idle_ms × 1000;
  • autoplay_unattended when start is 2, autoplay_grace_ms is above 0, there is no interaction observation, and T − T0 ≥ autoplay_grace_ms × 1000;
  • user and ended always.

6.3 The outcome

  • refuse: the reason and the record's index (§6.1).
  • accept: the number of records and of segment receipts; whether the log is open or terminated, with the reason and media of its terminate record; the totals and the unmetered counts, by role and class, recomputed from the segment receipts; the roles absent from every segment receipt; for video, the segment receipts whose rendition was within the declaration and those served under none_within; and, for video, what was delivered while the stream was unseen:
    • unseen time: the microseconds from the open record's t to the last record's during which it was unseen: the sum, over each run of observations in which it became unseen, of the time from the observation that made it unseen to the next observation that made it seen, or to the last record's t;
    • unwatched segments: the segment receipts at which the stream was unseen, with t − unseen since ≥ unseen_grace_ms × 1000: made after the sink's own grace had run out, with their totals and unmetered counts by role and class as above.

A sink that terminates at its grace has no unwatched segments, and one that never terminates has every segment it received past its grace among them. The figure is the log's own account: it rests on the sink's observations (§9), and its totals are per class, as every total here is.

7. Reasons

The codes this extension adds, each with where it is raised:

reason raised at
reason_unknown §3.9 step 1
display_invalid §3.9 step 3, §6.1 step 5
record_invalid §3.9 step 4
after_termination §4.3, §6.1 step 2
order_invalid §6.1 step 3
sink_changed §6.1 step 3
autoplay_against_setting §4.4, §6.1 step 4
media_order_invalid §6.1 steps 6.1 and 7.1
rendition_unknown §6.1 step 6.2
match_mismatch §6.1 step 6.3
rendition_above_panel capability-constraints §4.2, §6.1 step 6.4
reason_not_applicable §4.3, §6.1 step 7.2
reason_unsupported §4.3, §6.2, §6.1 step 7.3
totals_mismatch §3.7, §6.1 step 7.4

A verifier of this extension also raises truncated, malformed and unknown_version for a record that does not read, bad_key and bad_signature for its signature (the codes of live-events), and energy_invalid for a hop entry's class (live-metering §9).

8. Conformance

live-conformance/termination/ holds the corpus, in one file, vectors.json, written from fixed seeds by wai_stream_termination_vectors gen. It has matches, each a display declaration, a ladder and a rendition with the match and the admissible set capability-constraints §4.2 gives them: every exceedance alone and together, both orientations, a panel smaller than every rendition, a tie for the least, and a high-dynamic-range panel with PQ and HLG renditions. And it has logs, each a list of records with the outcome of §6.3:

  • Accepted: a video stream terminated as hidden after its grace, with a segment received past the grace counted unwatched, its hops metered under two classes and one unmetered; terminated as occluded and as display off; as idle, after interactions; as autoplayed with no interaction, its setting on and the platform's answer allowed-muted; stopped by the viewer; an audio stream that goes on while its document is hidden and ends; a log still open; a panel smaller than every rendition, served the least; display records that change the declaration mid-stream, down and back up, and the sink moving its rendition with them; a hidden document observed hidden twice, its grace running from the first; and a high-dynamic-range panel served a PQ rendition.
  • Refused: each reason of §7 at least once, each log a control of one defect, among them a segment receipt after a termination as hidden (after_termination), a termination as hidden before the grace and one while visible (reason_unsupported), a rendition above the panel while one within it was offered, and one kept after a display record shrank the panel (rendition_above_panel), and a total summed across two classes (totals_mismatch); and the codes of reading and of the signature.

A conforming verifier gives every match its match and every log its outcome, reason, index and facts. wai_stream_termination_vectors verify and the test tests/stream_termination.rs check that the file regenerates byte for byte, that wai-rs gives every case its stated outcome, that every reason of §7 refuses a log, and that §7 lists exactly the reference's codes. live-conformance/stream_termination_verify.py is the second implementation, written from this text and capability-constraints §4: it shares no code with wai-rs, verifies signatures with WAI's strict rule written from RFC 8032 (claim-conformance/_ed25519.py), gives every case the same outcome, and requires every mutated copy of the corpus to fail. The page runs the corpus through its wasm build, and its own records are verified on the host, in joule-moq-web's end-to-end scenario termination: a page whose tab goes to the background ends the video it receives with a terminate record, keeps the audio, and opens a new log when it is in front again.

9. Security and privacy considerations

  • A log is the sink's statement. Its signature and its chain make it attributable and ordered, and keep a record from being removed, reordered or added after the termination without the key. They do not make an observation true: a sink can state that a document was hidden when it was not. A verifier checks that the reason agrees with the observations the log holds, which binds the sink's statements to one another, not to the screen.
  • What a log reveals. When a viewer's tab was in the background, when the viewer interacted (at most once a second, nothing of the input), the panel's size and depth, the autoplay setting, and the stream. A service that collects logs learns that much about each playback session it collects from. The session key is fresh and derived from nothing that identifies the viewer (§4.1), so logs of two sessions do not link through it; a sink that makes a key per stream keeps two streams of one session apart as well. Where an operator needs to record a legal basis for collecting logs, jwp-receipts §2.4.6's lawful-basis claim is the place.
  • A display declaration is a fingerprint surface. Its members are among what a page already exposes (CSSOM View, Media Queries), and its refresh rate is measured, not read. Physical size is not available to a page and is 0 there.
  • Energy figures. A per-segment figure measured at the sink is a side channel on what the sink did, as browser-sink §13 says of a page's figures; a page receives one only from a meter its host attached. A figure's class rests on the session key alone: no key log lists that key with an energy authority (jwp-receipts §7.5), so a hardware class in a log is the sink's statement and nothing confirms it.
  • Not claimed. A termination saves the energy of the segments it stopped, which this record does not state: what was not sent was not measured. The unwatched totals are what was delivered and not seen, by the sink's account, per class.