Skip to main content

WAI Extension: Live Recording

Mirrored from the canonical text at commit f871af55 ().

Status: Draft. Registers the record wai.live.recording: a finished room session as one WAI package, and clips of it that prove what they are. The package is a directory of files and its listing, as a package of packaging §11 is; its components are the room (its descriptor, grants and key-log head, live-rooms), its events (each principal’s chain whole, live-events §8.6), one component for each participant’s take (capture-binding), and, optionally, the composite; and a signed recording manifest names each component by its digest, with the signer, the room and the session’s span on the room clock. A recording verifier gives complete and publishable for each purpose their verdicts. A clip is a span of one take, or one window of several takes aligned on the room clock; its signed clip record carries, for each take, the descriptors of the chunks it covers and a range proof that they are leaves of a commitment the device made while it captured, so a verifier holding the clip, the take’s commitments, the device’s key and the room descriptor knows the clip’s bytes are exactly those bytes of that take, unedited, without the take. The HLS and LL-CMAF presentation of a recording (§7) and the addressing of its chunks for a MoQ FETCH (§8.1) are mappings. A relay’s DVR window of a room’s tracks (§8.2 to §8.5) keeps them, with their receipts, for a viewer to seek back into by FETCH. No new transport and no new container. The reference implementation is the live_record module of wai-rs (feature live_record); the corpus is live-conformance/recordings/, and its second implementation is live-conformance/live_record_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Texts read for this revision: RFC 6962 §2.1 and RFC 9162 §2.1.1, §2.1.3 and §2.1.4 (the Merkle tree hash, inclusion proofs and their verification); RFC 8216 §3.3, §4.3.2.2, §4.3.2.5, §4.3.2.6, §4.3.3.1, §4.3.3.5 and §7; draft-pantos-hls-rfc8216bis-22 §3.1.2, §4.4.3.7, §4.4.3.8, §4.4.4.5 and §4.4.4.9; and draft-ietf-moq-transport-22 (MOQT) §2.4.1, §3.2, §3.2.1, §8.2, §8.7, §9.11 and §9.20.9. The two drafts are Internet-Drafts, and a section number of either in this text is that revision’s.

1. Scope

In scope: the recording package, its files and its components (§3); the recording manifest and recording verification, with the verdicts of complete and publishable (§4); the clip record, the range proof it carries, clip verification, and cutting a clip by chunks, by bytes or by a window of the room clock (§5); a clip across takes, its time correspondence and its bound (§6); the mapping of a recording onto HLS and LL-CMAF (§7); and the addressing of a recording’s chunks for a MoQ FETCH, with the DVR window a relay keeps of a room’s tracks and serves to FETCH (§8).

Out of scope: a transport; how a relay lays its DVR window out on disk; a muxer for a master that is not CMAF; the composition itself, which wai.live.compose defines (live-compose §8, a later extension); and what a party does with a verdict.

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

2. Terms

Integers are little-endian. A digest is SHA-256 (FIPS 180-4), 32 bytes. Ed25519 is RFC 8032’s, verified strictly (SPEC §7 Signatures).

3. The recording package

3.1 Files and the listing

A recording is a directory of these files and no other:

recording.wrm                          the recording manifest (§4)
room/descriptor.wrd                    the room descriptor
room/grant-<k>.wrg                     each room grant
room/key-log.json                      the head of the room authority's key log
events/<principal>.wev                 each principal's chain
takes/<take>/manifest.wtm              a take's manifest
takes/<take>/chunk-<index>.bin         each chunk of the take the package holds
takes/<take>/receipt-<k>.wur           the store's upload receipts
takes/<take>/consent-<k>.wcn           the consent records
composite/receipt-<digest>.wcr         the composite's receipts, optionally (§3.5)
composite/…                            and the objects they name
LISTING

LISTING is the listing (SPEC §3.1) of every other file, and its SHA-256 is the package digest, as a package of packaging §11 has its own. Every path is one a listing can carry. <principal> and <take> are a key and a take id as 64 lowercase hexadecimal digits, <index> a chunk’s index as ten decimal digits, and <k> a position as four decimal digits from 0000.

3.2 Components

A package has these components, each the files below one directory:

kindcomponentiddirectory
1the roomroomroom/
2the eventseventsevents/
3a takethe take id, 64 lowercase hexadecimal digitstakes/<take>/
4the compositecompositecomposite/

A component’s sha256 is the SHA-256 of the listing (SPEC §3.1) of its files, each by its path below the component’s directory. That is SPEC §7.1’s component identity for a component of several files: an id, of SPEC §7.1’s form, and the sha256 of its bytes, here serialised as the listing that SPEC §3.1 gives a set of several files, as a pin in the form wai.prior.set covers one. No other per-component digest is defined.

3.3 The room and the events

The room component holds descriptor.wrd, the room descriptor (live-rooms §3); one grant-<k>.wrg for each room grant (live-rooms §4), numbered from 0000 without a gap in the order a room verifier is given them (live-rooms §9.1); and key-log.json, the head of the room authority’s key log that the room verifier is given (live-rooms §5), when the descriptor names an origin. It holds no other file.

The events component holds one file for each principal whose chain the recording holds, named by the principal’s key: its chain whole, its records concatenated in its stream order (live-events §8.6). A record’s length follows from its own fields (live-events §3.1), so the file splits into records with no framing; a file that does not split into records that read (live-events §3.3), each of that principal, is not a chain. A room verifier is given the chains in the order of their files’ paths, ascending order of the principals’ keys, and that order is the chain index by which live-rooms §9.2’s merged order breaks ties.

3.4 A take

A take component’s directory is takes/<take>/, its id the take id (capture-binding §3.2). It holds manifest.wtm, the take manifest (capture-binding §7); one chunk-<index>.bin for each chunk of the master the package holds, the chunk’s bytes; the store’s upload receipts in their order as receipt-<k>.wur (capture-binding §6.2); and the consent records as consent-<k>.wcn (capture-binding §8.1). It holds no other file.

The chunk files are the take’s parts (capture-binding §6.1), given to the take verifier in the order of their indexes. A chunk the store never received has no file: the package holds what was uploaded, and the take’s complete verdict says whether that is every committed chunk. The master is its chunks concatenated in index order (capture-binding §4.1).

3.5 The composite

A recording MAY hold the room’s composite: the objects of a composed track, each with the composition receipt WCR1 its compositor signed for it, which wai.live.compose defines (live-compose §8). The component composite holds each receipt as receipt-<digest>.wcr, <digest> the receipt’s digest (SHA-256 over its bytes before its signature, live-compose §8.1) in 64 lowercase hexadecimal digits, and the objects the receipts name beside them. The recording manifest names the component by its sha256 like any other, so the listing binds each receipt’s bytes and its name its digest. A recording verifier of this revision checks that digest and reads nothing in the component: verifying a composition receipt is live-compose §8.3’s, a later extension, and this extension depends on nothing of it but the name and digest of its record.

4. The recording manifest

4.1 Layout

When the session is over, the room authority, or a principal that held the record power over the whole span (§4.3), signs a recording manifest, WRM1:

fieldsizemeaning
magic4WRM1 (ASCII)
version11 in this revision
room32the room id
signer32the key that signs the manifest
from8u64: the session span’s first instant, room clock in microseconds
to8u64: its last instant
claims1the claims the manifest makes: bit 0 complete
purposes1the purposes it claims every take publishable for (capture-binding §2)
region2ISO 3166-1 alpha-2: where the package is kept
components2 + …a u16 count, then each component: kind (1), the length of its id (1), the id, and its sha256 (32)
sig64Ed25519 by signer

The digest of a manifest is SHA-256 over its bytes before sig; its signature is over the 19 bytes wai:live-recording (ASCII) and 0x01, followed by the bytes the digest covers.

4.2 Reading and rules

A reader reads exactly the bytes it is given, as live-events §3.3 reads a record with WRM1 for WEV1 (truncated, malformed, unknown_version), and refuses a manifest that breaks any of these rules as recording_manifest_invalid:

It then refuses one whose signature does not verify as bad_key or bad_signature. Each id the rules admit is of SPEC §7.1’s form, and the order makes the manifest of one package one sequence of bytes.

4.3 Who signs

The room authority (the descriptor’s authority, live-rooms §3.1) may sign any span. Any other signer MUST hold the record power (live-rooms §3.3) at every instant of the span, from from to to inclusive.

A principal holds the power at room-clock instant t when, in the room state replayed to t (live-rooms §9.3: every join, grant_exchange and leave of it that the room verifier accepted with its t at or below t, in the merged order), it is present, and its grant in effect has the record power, is not revoked (live-rooms §5), and covers t: nbf ≤ ⌊(t0 + t) / 10^6⌋ < exp. So the grant took effect, by a join or a grant_exchange, at or before from, and nothing takes the power away before to: no leave, no grant_exchange to a grant without it, and no expiry.

A signer that does not hold it throughout is refused recording_signer_not_permitted, and the verifier states the first instant of the span at which it does not hold it: from when it held no grant with the power there; otherwise the t of the leave or the grant_exchange that took the power away, or the first instant its grant does not cover. One code with the instant, not one code per cause: the power over the span is one property, each way it lapses is already a record of the room (a lifecycle event, or a grant’s window) that the instant points at, and the instant is what a signer needs to know to seal a span it did hold the power over. A span that fails several ways fails first where the instant says.

The record power is the power to start and stop a recording, and sealing the session is the end of that act. Held only at the end, it would let a principal granted it just before the end seal a session it never held the power to record. A recorder that gained the power partway through signs a manifest whose span starts no earlier than its grant took effect; the package still holds the room’s chains whole (§3.3), and §4.4 step 6 holds every take it holds to that span. A device needs no record power to commit to its take, and a device whose grant has none cannot seal the room’s recording.

A revocation in the key log is not bounded by a time (live-rooms §5): the room verifier refuses every use of a revoked grant, so a head that revokes the signer’s grant refuses the recording at the room (§4.4 step 4), before this rule is reached.

4.4 Verification

A recording verifier is given a package’s files by path, and an evaluation instant at in seconds since 1970-01-01T00:00:00Z, which it gives each take verifier (capture-binding §9.1). It runs these steps and stops at the first refusal, which it reports with the reason and where:

  1. The listing. LISTING is present and is the listing of every other file: otherwise listing_mismatch, at listing.
  2. The manifest. recording.wrm is present, otherwise recording_manifest_invalid, and it reads (§4.2); at manifest.
  3. The components. For each component of the manifest, in its order: its directory holds a file, otherwise component_invalid, and the SHA-256 of its listing is its sha256, otherwise component_digest_mismatch; at that component. Then every file is LISTING, recording.wrm, or a file below a component’s directory: otherwise component_unlisted, at listing.
  4. The room. The room component holds the files of §3.3 and no other, and each file of the events component is a chain of the principal it names (§3.3): otherwise component_invalid, at that component. The room verifier (live-rooms §9) is given the descriptor, the grants, the key-log head (or none) and the chains: a refusal is the recording’s, at room with the room verifier’s own place. A gap is not a refusal: the steps go on, and §4.5 says what a gap fails. The manifest’s room is the room id: otherwise wrong_room, at manifest.
  5. The signer (§4.3): otherwise recording_signer_not_permitted, at signer with the first instant of the span at which it does not hold the record power. The region: where the room’s recording policy pins regions (live-rooms §3.7), region is one of them, otherwise store_outside_region, capture-binding §6.3’s code, at manifest.
  6. The takes. For each take component, in the manifest’s order: it holds the files of §3.4 and no other, otherwise component_invalid; the take verifier (capture-binding §9) is given the room as in step 4, the take manifest, the parts, the receipts, the consents and at, and its refusal is the recording’s, at take with the component’s index and the take verifier’s own place; the take id it reads is the component’s id, otherwise component_invalid; and the take lies within the span, its start (the record_start it answers) at or after from and its last held commitment at or before to, otherwise outside_span; at that component. The records before from or after to that the package holds are the room’s chains whole, which the room verifier replays the room from; the manifest states nothing of them.
  7. The verdicts (§4.5).

4.5 Verdicts and outcome

complete holds when the recording holds every take of the session that starts within the span, each whole, and every event the room verifier was offered: the first reason below that applies.

  1. delivery_gap: the room verifier holds a gap (live-rooms §9.4).
  2. take_omitted: a take of the session whose record_start lies within the span has no component. A take of the session is the take id (capture-binding §3.2) that a record_start the room verifier accepted (live-events §4.9) names for its target, where the room holds a take_commit of that target whose payload reads (capture-binding §5.2) and names that take: a start its device answered.
  3. For each take component, in the manifest’s order: its single-take verdict fails with delivery_gap, commit_chain_broken or take_not_closed, so its commitments are not one unbroken chain from the opening commitment to the closing one: that reason; or its complete verdict fails: that reason (capture-binding §9.4).

publishable for purpose p holds when every take is publishable for p: for the first take component, in the manifest’s order, whose publishable for p fails, that reason.

A recording verification gives one outcome:

5. Clips

5.1 The clip record

A clip is a span of one take, or one window of several takes aligned on the room clock (§6). A cutter signs a clip record, WCL1:

fieldsizemeaning
magic4WCL1 (ASCII)
version11 in this revision
room32the room id
window_in8u64: the first room-clock instant the clip presents, in microseconds
window_out8u64: the instant it presents up to (excluded)
parts1 + …a u8 count k, then each part as below
signer32the cutter’s key
sig64Ed25519 by signer

Each part:

fieldsizemeaning
device32the device’s key
take32the take id
head32the digest of the take_commit event whose commitment the proof is against
size8u64: that commitment’s size
root32that commitment’s root
first8u64: the index of the first chunk the part covers
count4u32: the number of chunks it covers
trim_in8u64: the bytes of the first chunk before the clip’s bytes
trim_out8u64: the bytes of the last chunk after them
chunks64 × countthe descriptor of each chunk it covers (capture-binding §4.1), in index order
proof_len1the number of nodes in proof
proof32 × proof_lenthe range proof (§5.3)

The digest of a clip record is SHA-256 over its bytes before sig; its signature is over the 14 bytes wai:live-clip (ASCII) and 0x01, followed by the bytes the digest covers. A part’s bytes are its chunks’ bytes concatenated, less trim_in bytes at the start and trim_out at the end; the clip’s bytes are its parts’.

5.2 Reading and rules

A reader reads exactly the bytes it is given, as live-events §3.3 reads a record with WCL1 for WEV1 (truncated, malformed, unknown_version), and refuses a record that breaks any of these rules as clip_record_invalid:

It then refuses one whose signature does not verify as bad_key or bad_signature.

Partial chunks. A clip whose range begins or ends inside a chunk carries that whole chunk and states the bytes it leaves out, as trim_in and trim_out. A leaf commits to a chunk’s digest over all its bytes (capture-binding §4.2), so a part of a chunk can never be proved on its own: with the whole chunk the proof stays exact, and the trims say which of the proved bytes the clip presents. Refusing a range that does not fall on chunk boundaries would make a byte range of the master, or a range request, unprovable unless it happened to land on them. A trim is in bytes, which have no committed time within a chunk, so a trimmed clip’s window is its chunks’ span (§5.4 step 3j), and trims are a single take’s: an aligned clip (§6) presents a window of time, not of bytes. A cutter that must not disclose the bytes outside the range cuts on chunk boundaries, since the trimmed bytes travel with the clip.

5.3 The range proof

A part proves its chunks by one proof over the run of leaves they are, against the head. With D[n] the take tree’s leaves (capture-binding §4.2) under the head’s size n, MTH RFC 9162 §2.1.1’s Merkle tree hash with the take tree’s node, and k the largest power of two below n, the proof of leaves a to b − 1 (0 ≤ a < b ≤ n) is:

RANGE(a, b, D[n]) = {}                                         a = 0 and b = n
                  = RANGE(a, b, D[0:k]) : MTH(D[k:n])           b ≤ k
                  = RANGE(a − k, b − k, D[k:n]) : MTH(D[0:k])   a ≥ k
                  = RANGE(a, k, D[0:k]) : RANGE(0, b − k, D[k:n])   otherwise

where : appends. A run of one leaf is never split, so RANGE(i, i + 1, D[n]) is RFC 6962 §2.1.1’s PATH(i, D[n]), the inclusion proof RFC 9162 §2.1.3.1 generates, node for node. A proof of a whole tree is empty. A proof has at most 2⌈log₂ n⌉ nodes, at most 128 for any size a commitment can state, and every node of it is a node of the inclusion proof of a leaf in the run.

A verifier given the leaves L of the run (b − a of them), the size n and the nodes, used in order from the first, computes ROOT(a, b, n, L):

ROOT(a, b, n, L) = MTH(L)                                      a = 0 and b = n
                 = node(ROOT(a, b, k, L), next)                 b ≤ k
                 = node(next, ROOT(a − k, b − k, n − k, L))     a ≥ k
                 = node(ROOT(a, k, k, L[0 : k − a]),
                        ROOT(0, b − k, n − k, L[k − a :]))      otherwise

where next is the next unused node, and node is the take tree’s (staged-measurement Appendix C). The proof verifies when no next is asked for past the last node, every node is used, and ROOT is the head’s root. For a run of one leaf this is RFC 9162 §2.1.3.2’s check of an inclusion proof, and gives its answer.

The size is the head’s, which the device signed, never the cutter’s: as for an inclusion proof, the same nodes can reach one root under two sizes (staged-measurement Appendix C), so a size the cutter chose would let it choose the leaves’ positions. Under the head’s size the recursion fixes which subtree each node stands for; the leaf and node domains differ, so no node passes for a leaf; and each leaf binds its chunk’s index, capture range, length and digest (capture-binding §4.2). A proof that verifies therefore shows, short of a collision of SHA-256, that the run’s descriptors are the head’s leaves a to b − 1.

5.4 Verification

A clip verifier is given the room descriptor; the clip record; and, for each part in order, the device’s key, the take’s commitments (its take_commit events, from its opening one to the head, in the order of the take’s chain), and the bytes of each chunk the part covers, each present or absent. It is not given the take manifest, any other chunk, the upload, or the room’s other events. It runs these steps and stops at the first refusal, which it reports with the reason and where:

  1. The descriptor reads (live-rooms §3.4): its refusal, at descriptor. The room id is its digest, and I and J are its recording policy’s (live-rooms §3.7).
  2. The record reads (§5.2), at clip. Its room is the room id, otherwise wrong_room; and the verifier is given one input for each part, otherwise clip_record_invalid; at clip.
  3. Each part, in order, steps (a) to (k):
    • (a) The key given is the part’s device: otherwise clip_device_mismatch, at the part.
    • (b) Each commitment given reads (live-events §3.3), names the room (wrong_room), is the device’s (clip_device_mismatch), verifies (bad_key, bad_signature), is a take_commit whose payload reads (capture-binding §5.2, commit_malformed), and names the part’s take (clip_take_mismatch); at that commitment.
    • (c) The commitments are a chain (capture-binding §5.3): the first is an opening one, and each later one has the prev, first and n that follow its predecessor, which is not a closing one, and is itself not an opening one. A later one whose n is more than one above its predecessor’s is delivery_gap; any other break is commit_chain_broken. Then each extends its predecessor (capture-binding §4.3), otherwise root_not_extending. At the first commitment that fails, the chain checked over all of them before any extension, as capture-binding §9.4 orders them. None given is commit_chain_broken at the part.
    • (d) The last commitment given is the head: its digest is head, and its size and root are the part’s size and root. Otherwise tree_head_not_committed, at the part: a tree the take never committed proves nothing of it.
    • (e) first + count is at most size: otherwise clip_out_of_range, at the part.
    • (f) The proof (§5.3) shows the part’s descriptors are the head’s leaves first onward, under size: otherwise clip_not_included, at the part.
    • (g) Every commitment’s offset is within J of the opening one’s, o (capture-binding §9.4 single-take 6): otherwise clock_unstable, at that commitment.
    • (h) The chunks tile their capture, each start below its end and each start the previous chunk’s end (capture_discontinuous), and none spans more than I (chunk_too_long) (single-take 7, 8); at the part.
    • (i) With each chunk mapped by o (capture-binding §9.3) and t the room-clock instant of the commitment that adds it (the one whose first ≤ index < size): no chunk has t + J below its mapped end (commit_early), and then none has t above its mapped end plus I plus J (commit_late) (single-take 9, 10); at the part.
    • (j) The window: with the part’s span the first chunk’s mapped start to the last one’s mapped end, the span covers the window, the first chunk ends after window_in and the last begins before window_out; and a trimmed part’s window is its span. Otherwise clip_window_invalid, at the part.
    • (k) The bytes: for each chunk in order, its bytes are given (clip_chunk_missing) and are that chunk’s, their digest and length its descriptor’s; bytes that are another covered chunk’s are chunk_reordered, any others chunk_altered; at that chunk. Bytes given past the last chunk are chunk_extra, at the chunk past it.

Steps (b) to (k) are single-take over the span: the rules of capture-binding §9.4 single-take that hold of the chunks the clip covers, checked from the commitments up to the head. A clip does not need rule 4 (closed), since it may be cut while the take runs (§8); nor rule 5 (the manifest), since inclusion binds its chunks to the head instead; nor rule 9’s clauses on the start and the stop, whose events it is not given. A take whose single-take fails only at chunks outside a span, as by a late commitment, still gives clips of that span that verify.

A clip that passes is accepted, and the verifier states: the record’s digest and signer; the window; the bound (§6.2); and for each part its take, first, count, the take’s offset o, its span, the window on its device’s clock (§6.1), the length and SHA-256 of the bytes it presents, and its FETCH address (§8). Where the take’s single-take holds, an accepted clip’s descriptors are its manifest’s at those indexes, since rule 5 binds the manifest’s chunks to every commitment’s root; the clip verifier does not need the manifest. A clip over chunks a moderation decision restricts is verified as live-moderation §8.3 has it: step (k) passes over the restricted chunks, whose descriptors the proof still covers.

5.5 Cutting a clip

A cutter holds a take’s manifest, its commitments and the chunks it covers. It picks a head: the take’s closing commitment, or, while the take runs, any commitment whose size covers the range. It cuts:

A clip cut by chunks or bytes presents its chunks’ span as its window. The proof is §5.3’s over the manifest’s descriptors up to the head’s size; the cutter signs the record.

6. Across takes

6.1 Time correspondence

An aligned clip has a part for each take it spans, over one window of the room clock. Each take is placed on the room clock by its own mapping, the offset o of its opening commitment (capture-binding §9.3): a room-clock instant t in the window is the device-clock instant t − o_p in part p, in the chunk whose capture range holds it, at its position in that chunk and in the master (capture-binding §10). Two parts p and q correspond at t: device instant d in p is d + o_p − o_q in q. The verifier states each part’s window on its device’s clock, [window_in − o_p, window_out − o_p).

6.2 The bound

A part’s mapping is held, over every commitment up to its head, to within J of the offset the device measured at that commitment (step 3g), and each chunk it covers was committed within [−J, I + J] of its mapped end on the room clock (step 3i). The clip states its bound, bound_us: J for a clip of one take, its mapping’s to the room clock; and 2J for an aligned clip, between any two of its parts, each strays by J from the room clock as its device measured it. A sink that presents the parts of an aligned clip together aligns them within that bound of what the devices measured. The measurement itself, and each commitment’s t, are their authors’ statements (live-events §11), held by the room’s other records as capture-binding §14 says.

7. HLS and LL-CMAF

This section is a mapping: what a standard HLS client plays of a recording, with no new format. Where a take’s master is CMAF (ISO/IEC 23000-19), cut as capture-binding §4.1 cuts it, chunk 0 is the CMAF header and the first CMAF segment, and each later chunk one CMAF segment, so the take’s chunk files are already an HLS presentation of fragmented MPEG-4 segments (RFC 8216 §3.3; draft-pantos-hls-rfc8216bis-22 §3.1.2):

A master that is not CMAF is played this way only through a muxer for its codec, which this extension does not define. What this revision implements of this section: nothing. The corpus’s masters are not CMAF, the reference has no muxer for a device’s master (packaging §7’s CMAF writer writes a floor’s AV1 fragments), and it writes no playlist.

8. DVR over MoQ FETCH

This section fixes how a recording’s chunks are addressed for a FETCH (§8.1), and what a relay that keeps a window of a room’s tracks, a DVR window, does with it: what it keeps and evicts (§8.2), how it answers a FETCH from it (§8.3), what moderation withholds from it (§8.4), and how it keeps it across a restart (§8.5); and the window’s presentation over HTTP (§8.6).

8.1 Addressing a take

A take’s track kept in a DVR window is kept and answered as any other track (§8.2, §8.3), so a clip’s part is a FETCH the window answers.

8.2 The window

A relay MAY keep a window of the tracks of a room: its media tracks with their receipts tracks, its events tracks, its catalogs and its takes.

8.3 Answering a FETCH

8.4 Moderation

What live-moderation withholds is withheld from the window, as §6.3 there has a relay withhold it in answer to a FETCH:

8.5 Across a restart

A window kept across a restart is checked again before it is served. Every object is checked against its receipt: a media object of a track with a receipts track against group g’s receipts, as jwp-receipts §4.1 checks one at the door (the group receipt’s signature under the trust the relay holds for the namespace, the object’s receipt at its position, its Merkle proof to the signed root, its content hash); an events-track object’s records each under its principal (live-events §3.2). An object that does not verify, or whose stored bytes do not hold, is dropped and counted, and never served: a FETCH that reaches it is told End of Unknown Range (§8.3). How a relay lays the window out on disk is its own; the reference relay’s layout and its durability rule are in its README.

8.6 Over HTTP

The window is a time-shift buffer. In DASH (ISO/IEC 23009-1) its age is the MPD’s timeShiftBufferDepth. In HLS a live Media Playlist keeps the window’s segments and removes them from its start as the window evicts their groups; EXT-X-PLAYLIST-TYPE EVENT (RFC 8216 §4.3.3.5), whose segments are added only at the end, fits only a window that holds the session from its start and evicts nothing.

What this revision implements of §8.2 to §8.5: the reference relay, joule-moq-relay (joule-code/joule-code-rs/crates/joule-moq-relay), keeps a window per namespace prefix and its tests show each rule above. §8.6 is a mapping, and nothing writes it.

9. Reasons

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

reasonraised at
listing_mismatch§4.4 step 1
recording_manifest_invalid§4.2, §4.4 step 2
component_invalid§4.4 steps 3, 4 and 6
component_digest_mismatch§4.4 step 3; SPEC §7.1’s code, for a component of a recording
component_unlisted§4.4 step 3
recording_signer_not_permitted§4.3, §4.4 step 5, with the first instant of the span without the power
outside_span§4.4 step 6
take_omitted§4.5 complete 2
clip_record_invalid§5.2, §5.4 step 2
clip_device_mismatch§5.4 steps 3a, 3b
clip_take_mismatch§5.4 step 3b
tree_head_not_committed§5.4 step 3d
clip_out_of_range§5.4 step 3e
clip_not_included§5.4 step 3f
clip_window_invalid§5.4 step 3j
clip_chunk_missing§5.4 step 3k

take_omitted is a verdict; the rest are refusals. A recording verifier also raises every code a room verifier or a take verifier raises (live-rooms §11, capture-binding §12, and the live-events codes they list), at the room or the take; truncated, malformed, unknown_version, bad_key and bad_signature for a manifest that does not read or whose signature fails; wrong_room for a manifest of another room; store_outside_region for a package kept outside the room’s region pin; and delivery_gap and every verdict of capture-binding §12 as a verdict. A clip verifier also raises the codes of a descriptor that does not read (live-rooms §11); truncated, malformed, unknown_version, bad_key, bad_signature and wrong_room for its record and its commitments; and commit_malformed, delivery_gap, commit_chain_broken, root_not_extending, clock_unstable, capture_discontinuous, chunk_too_long, commit_early, commit_late, chunk_reordered, chunk_altered and chunk_extra at §5.4’s steps, each with the meaning capture-binding §12 gives it.

10. Conformance

live-conformance/recordings/ holds the corpus, in one file, vectors.json, written from fixed seeds by wai_live_record_vectors gen. Its recordings are packages, each with its files and an evaluation instant and the outcome §4.5 gives them; its clips are each a room descriptor, a clip record and, per part, the device’s key, the commitments and the chunks’ bytes, with the outcome §5.4 gives them.

A conforming verifier gives every vector the outcome, the reason, the place and the data the corpus states. wai_live_record_vectors verify and the test tests/live_record.rs check that the file regenerates byte for byte, that wai-rs gives every vector its outcome, that every reason of §9 appears in the corpus, that §9 lists exactly the reference’s codes, and that every take the corpus says it reuses is capture binding’s, byte for byte, against live-conformance/takes/vectors.json. live-conformance/live_record_verify.py is the second implementation, written from this text: it shares no code with wai-rs; it checks range proofs by §5.3’s recursion and, for a run of one chunk, by RFC 9162 §2.1.3.2’s algorithm as that RFC states it; it verifies signatures with WAI’s strict rule (claim-conformance/_ed25519.py), each take with the capture-binding corpus’s second implementation (capture_take_verify.py) and the room with the live-rooms corpus’s (live_rooms_verify.py); it gives every vector the same outcome; and it mutates copies of the corpus and requires every copy to fail.

11. Security and privacy considerations