WAI Extension: Stream Termination
Mirrored from the canonical text at commit 260cbd09 ().
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 thestream_terminationmodule ofwai-rs(featurestream_termination), whichwai/wai-live-webbuilds for a page; the page’s own code isjoule-code/joule-code-rs/crates/joule-moq-web/web/room/termination.js; the corpus islive-conformance/termination/, and its second implementation islive-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
visibilitychangeevent) and §6.4 (user activation: the activation-triggering input events and theUserActivationinterface); 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
tare 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:
- as
reason_unknown, a terminate record whosereasonis not 1 to 7; - as
energy_invalid, a hop entry whose class breaks live-metering §3.6’senergy_invalidrules over itsjoules: the tag is 0 exactly whenjoulesis 0; the tag is 0, 2, 3, 4 or 5, never the legacy 1 (energy-measurement §2.6); for tags 4 and 5,relative_ppmandwindow_usare above 0 andjoules×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; - as
display_invalid, an open record of video or a display record whose declaration breaks capability-constraints §4.1:shortfrom 1 tolong,refresh_milliat least 1,bit_depth1 to 16,hdr0 or 1; and an open record of audio whose declaration is not 22 zero bytes; - as
record_invalid, a record that breaks any of these:- open:
media,startandautoplayare 1 or 2,ua_policy0 to 3; a video stream’s ladder holds 1 to 32 renditions, each withwidth,heightandframe_rate_milliat least 1 andbit_depth1 to 16; an audio stream’s holds none; - segment:
objectsat least 1;media_startbelowmedia_end; no bit ofmatchabove bit 4; at most 4 hop entries, eachrole1 to 4, in strictly ascending order; - observation:
signal1 to 4;value1 or 2 for signals 1 to 3, and 1 for signal 4; - terminate: each total’s
role1 to 4,tag2 to 5 andfiguresat least 1, in the order §3.7 gives; each unmetered entry’srole1 to 4 andcountat least 1, in its order.
- open:
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:
- Reading (§3.9):
truncated,malformed,unknown_version,reason_unknown,energy_invalid,display_invalid,record_invalid,bad_key,bad_signature. - After the termination: a record after a terminate record is
after_termination. - Order: the first record is an open record and no other is; each
seqis the record’s index; eachprevis the digest of the record before it, and 32 zero bytes for the first; eachtis at least thetbefore it. Otherwiseorder_invalid; a log of no record is refusedorder_invalidat index 0. Each record’ssinkis the first’s: otherwisesink_changed. - The open record:
startis 2 whileautoplayis 2 orua_policyis 3:autoplay_against_setting. - A display record in the log of an audio stream:
display_invalid. - A segment receipt:
- its
media_startis at least themedia_endof the segment receipt before it, where there is one: otherwisemedia_order_invalid; - for video, its
renditionis below the ladder’s count, and for audio it is 255: otherwiserendition_unknown; - for video, its
matchis the match capability-constraints §4.2 gives its rendition against the declaration in effect over the ladder, and for audio it is 0: otherwisematch_mismatch; - for video, its rendition is admissible: otherwise
rendition_above_panel.
- its
- The terminate record:
- where a segment receipt precedes it, its
medialies from the last one’smedia_startto itsmedia_end, both included: otherwisemedia_order_invalid; - its reason is
hidden,occludedordisplay_offin the log of an audio stream:reason_not_applicable; - the state of §6.2 at its
tdoes not support its reason by the table of §4.3:reason_unsupported; - its
segments,totalsandunmeteredare not those the segment receipts before it give (§3.7):totals_mismatch.
- where a segment receipt precedes it, its
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:
hiddenis supported when visibility is hidden andT− since ≥unseen_grace_ms× 1000;occludedwhen occlusion is occluded, anddisplay_offwhen display power is off, each with the same bound;idlewhenidle_msis above 0 andT− (the last interaction, orT0where there is none) ≥idle_ms× 1000;autoplay_unattendedwhenstartis 2,autoplay_grace_msis above 0, there is no interaction observation, andT−T0≥autoplay_grace_ms× 1000;userandendedalways.
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
mediaof 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 undernone_within; and, for video, what was delivered while the stream was unseen:- unseen time: the microseconds from the open record’s
tto 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’st; - 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.
- unseen time: the microseconds from the open record’s
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-basisclaim 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.