Some checks failed
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 6s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 10s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Has been skipped
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Has been skipped
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 3m10s
joule-code conformance gate / joule-moq-relay, every target (pull_request) Successful in 2m44s
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Successful in 2m7s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
wai cross-architecture byte-equality / does this change touch it (pull_request) Successful in 15s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Has been skipped
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Has been skipped
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Has been skipped
WAI conformance / does this change touch it (pull_request) Successful in 18s
WAI energy receipts / does this change touch it (pull_request) Successful in 15s
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Successful in 11s
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Has been skipped
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Has been skipped
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Successful in 6s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Has been skipped
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Has been skipped
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 6s
wai-pack / wai-pack · linux (pull_request) Has been skipped
wai-pack / wai-pack · linux-arm64 (pull_request) Has been skipped
wai-pack / wai-pack · macos (pull_request) Has been skipped
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been skipped
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been skipped
wai-pack / wai-pack · dependency licences (pull_request) Has been skipped
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m36s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 5s
wai-quantum studio and desktop / studio · browser (pull_request) Has been skipped
wai-quantum studio and desktop / desktop app · macos (pull_request) Has been skipped
wai-web wasm32 / does this change touch it (pull_request) Successful in 8s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Has been skipped
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 1m20s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 18s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 5m21s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 11s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 27s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m38s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 9m14s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m56s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 49s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 19m40s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 11m10s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 31m12s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 20m1s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 6m18s
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
wai/CHANGELOG.md: both entries kept, this branch's above main's. Co-Authored-By: Claude Opus 5.5 <[email protected]>
554 lines
36 KiB
Markdown
554 lines
36 KiB
Markdown
# WAI Extension: MoQ Streaming Format
|
||
|
||
> Status: Draft, and deliberately tracking rather than pinning — MOQT is
|
||
> pre-Working-Group-Last-Call and its text still moves. Declares WAI as a
|
||
> **streaming format** over Media over QUIC Transport, so a WAI envelope is a
|
||
> first-class MOQT object rather than an opaque payload inside someone else's
|
||
> container. No new transport: WAI defines none by charter, and this extension
|
||
> does not change that. Field semantics and rules over them only — never MOQT's
|
||
> byte encoding.
|
||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||
>
|
||
> Drafts read for this revision: `draft-ietf-moq-transport-22` (MOQT),
|
||
> `draft-ietf-moq-msf-01` (MSF) and `draft-ietf-moq-cmsf-01` (CMSF), all
|
||
> Internet-Drafts of the IETF MoQ working group, none past Working Group Last Call.
|
||
> A section number of MOQT, MSF or CMSF in this text is one of those revisions; §6
|
||
> says what happens when they move.
|
||
|
||
## 1. Why a streaming format
|
||
|
||
MoQ separates two things that other stacks fuse:
|
||
|
||
- **MOQT** (`draft-ietf-moq-transport`) moves objects, in groups, on tracks, and
|
||
orders them by priority. It does not know what an object means.
|
||
- **MSF** (`draft-ietf-moq-msf`) says what objects mean: it fragments a stream
|
||
into objects, maps each object to a stream of its own, and declares what a
|
||
publisher emits in a **catalog**.
|
||
|
||
WAI's confidentiality extension seals MoQ objects end to end (secure objects keyed by
|
||
MLS; see [confidentiality](confidentiality.md)), and leaves what the objects mean to
|
||
this one. This one treats MoQ as what it is — a two-layer design with an open slot at the upper
|
||
layer, which is precisely where a container that carries capability-dispatched
|
||
objects belongs.
|
||
|
||
The alignment is structural, not aspirational. MSF fragments a stream into
|
||
objects in groups; WAI envelopes are already objects, and
|
||
[jwp-receipts](jwp-receipts.md) already commits per object and signs per group.
|
||
The two layers agree on the unit without either being changed.
|
||
|
||
## 2. Precedent: the slot is open, and cheaply
|
||
|
||
MSF's catalog names a format in its `packaging` field, over a value set MSF's own
|
||
text fixes (MSF §5.2.4: `loc`, `mediatimeline`, `eventtimeline`, `moqlog`,
|
||
`moqmetrics`; the catalog itself is the track named `catalog`, MSF §5). There is
|
||
no central streaming-format registry. Read alone, that is closed.
|
||
|
||
`draft-ietf-moq-cmsf` shows it is not. CMSF is a sibling Internet-Draft that
|
||
declares `packaging` value `cmaf`, stating that it *extends the allowed packaging
|
||
values defined in MSF*; inherits MSF wholesale — *all of the specifications,
|
||
requirements and terminology defined in MSF apply to implementations of this
|
||
extension*; adds two track-level catalog fields; and registers nothing: **this
|
||
document has no IANA actions**. LOCMAF is a second instance of the same move.
|
||
|
||
So the path is a sibling draft with no registry action and no permission sought.
|
||
This extension is the WAI-side statement of what such a draft would say.
|
||
|
||
## 3. The packaging value
|
||
|
||
A track carrying WAI envelopes **MUST** declare `packaging` value `"wai"`.
|
||
|
||
- Every object on such a track **MUST** be exactly one WAI envelope (`WAI1`
|
||
magic + manifest + payload), whole. An object carrying a fragment of an
|
||
envelope, or more than one, is not this packaging.
|
||
- The object boundary **MUST** be the envelope boundary. This is what makes the
|
||
receipt's per-object commitment (`jwp-receipts` §2.1) and MOQT's object
|
||
identity the same unit; if they diverge, every guarantee that composes them is
|
||
void.
|
||
- A sink that does not recognise `"wai"` **MUST NOT** attempt to interpret the
|
||
objects. MSF's tolerant-parser rule covers unknown *fields*; it does not
|
||
license guessing at unknown *packaging*.
|
||
|
||
## 4. Catalog declaration
|
||
|
||
A WAI track's catalog entry **MUST** declare, in addition to MSF's required
|
||
fields, the capabilities a sink needs to dispatch what the track carries:
|
||
|
||
| field | type | meaning |
|
||
|---|---|---|
|
||
| `wai_capabilities` | array of string | Capability strings (WAI SPEC.md §4) that objects on this track may dispatch to. |
|
||
| `wai_determinism` | string | The weakest tier any object on the track claims — see [determinism-tiers](determinism-tiers.md). Absent means no tier is claimed. |
|
||
| `wai_receipts` | boolean | Whether objects carry receipts per §5. |
|
||
| `wai_receipt_alias` | integer | The `track_alias` the publisher signs in this track's receipts ([jwp-receipts](jwp-receipts.md) §2.4.1), from 0 to 2^53 − 1. |
|
||
| `wai_receipts_track` | string | The name, in this track's namespace, of the track that carries this track's receipts (§4.1). |
|
||
| `wai_ai_usage` | object | The usage statement of the track's objects ([ai-usage](ai-usage.md) §3), or of the room record in effect for a room's track. Absent means the catalog states none. |
|
||
| `wai_priors` | array of object | OPTIONAL. The parameter sets the track's objects pin (SPEC §3.1), by digest, each with the tier it is offered at (§4.2). Absent means the catalog states none. |
|
||
|
||
- `wai_receipt_alias` names the track's receipt chain, which a verifier keeps per
|
||
publisher key and `track_alias` (jwp-receipts §3 step 3). MOQT's own Track Alias is
|
||
chosen for each session by the publisher of that session, a relay included (MOQT
|
||
§3.1.3), so past the first hop it is not the number the receipts sign; a sink
|
||
finds a track's chain by this member. It is an integer written as one (SPEC §2:
|
||
not `8.0`, not `"8"`), compared as a number; a catalog that carries any other value
|
||
for it, on any track, carries no stage set with receipts (§4.1, check 6). Two
|
||
tracks of one catalog **MUST NOT** carry the same value: each track that declares
|
||
receipts has a chain of its own (§4.1). §4.1 requires it, and
|
||
`wai_receipts_track`, on the tracks of a stage set that declare receipts; on
|
||
another track both are OPTIONAL.
|
||
- `wai_capabilities` **MUST** be exhaustive for the track. A publisher that
|
||
dispatches to a capability it did not declare has broken the catalog's
|
||
purpose, which is letting a sink decide **before** subscribing whether it can
|
||
play what it is about to receive.
|
||
- A sink **MUST NOT** subscribe on the assumption that an undeclared capability
|
||
will not appear, and **MUST** treat an undeclared capability at dispatch as a
|
||
protocol error rather than falling back silently.
|
||
- `wai_determinism` is the **weakest** tier on the track, not the strongest.
|
||
Declaring the strongest would let a track advertise a guarantee most of its
|
||
objects do not meet.
|
||
- `wai_ai_usage` lets an agent know a track's AI usage preference before it
|
||
subscribes. The catalog is unsigned and the objects' own statements are
|
||
signed with them: a catalog whose statement differs from an object's is refused
|
||
(`catalog_conflicts`), and the object's governs (ai-usage §4.5, §5.3).
|
||
|
||
These are custom catalog fields under MSF's extension rule (MSF §5: custom field
|
||
names "MUST NOT collide" with MSF's, and a parser ignores a field it does not
|
||
understand); they are namespaced by their `wai_` prefix and MUST NOT collide with
|
||
MSF-defined names.
|
||
|
||
### 4.1 Staged tracks
|
||
|
||
A stage set ([staged-delivery](staged-delivery.md)) is carried as **one track per
|
||
layer**: a **base track**, which carries the set's bases, and a **layer track** for
|
||
each layer index, which carries the layers of that index. Every track keeps §3: each
|
||
object is one whole `WAI1` envelope. A subscriber chooses its stage by the tracks it
|
||
subscribes to; a relay forwards tracks and never reads an envelope.
|
||
|
||
**The catalog.** Each track of a set **MUST** carry, in its catalog entry,
|
||
|
||
```json
|
||
"wai_staged": { "set": "cam1", "layer": 1, "lead_ms": 40 }
|
||
```
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `set` | non-empty string | The set's name: the same on every track of the set. |
|
||
| `layer` | integer, 0–255 | 0 on a base track; *k* on a track that carries layer *k*. |
|
||
| `lead_ms` | integer from 0 to 2^53 − 1, or `null` | On a layer track of a live set, the lead of its layer (staged-delivery §8): the time before a unit's presentation by which the layer must arrive to be applied to it. `null` on a base track. A sink reads it only while the set is live: when `isLive` is false it ignores it, as MSF §5.2.8 has a sink ignore `targetLatency`. |
|
||
|
||
A set's extent is the tracks present: its layers are 0 to the highest layer a track
|
||
of the set states, each with at least one track. The catalog states no count of
|
||
layers, so an MSF delta update (MSF §5.1.6, §5.3), which adds and removes tracks and
|
||
changes none it declared before, changes a set's extent by adding or removing a layer
|
||
track alone. More than one track **MAY** carry one layer, as renditions of it. Each
|
||
rendition is a track of its own, with a receipt chain of its own: a clone (MSF §5.1.6)
|
||
inherits every member of its parent but its name, so a clone of a track that declares
|
||
receipts **MUST** override `wai_receipt_alias` and `wai_receipts_track` (*Receipts*,
|
||
below).
|
||
|
||
A layer track's `wai_capabilities` lists the refinement capabilities its envelopes
|
||
carry; a sink that does not implement staged delivery finds them unsupported (SPEC
|
||
§4) and has no reason to subscribe. A member of `wai_staged` this revision does not
|
||
define is ignored, an earlier revision's `layers` among them. A publisher puts on a
|
||
track of layer *k* only envelopes whose `refines.layer` is *k*, and on a base track
|
||
only envelopes with no `refines`; a sink applies an envelope by its own `refines`
|
||
(staged-delivery §4.1, §13), whichever track it came on.
|
||
|
||
**Names and types.** A track is named by its namespace and its name (MSF §5.2.2,
|
||
§5.2.3); one that declares no namespace inherits the catalog track's (MSF §5.2.2). Two
|
||
tracks of a catalog **MUST NOT** share one namespace|name pair, and the tracks of a set
|
||
**MUST** share one namespace. The check below needs the catalog track's namespace,
|
||
which a sink knows, since it subscribed to the catalog track: a checker that does not
|
||
know it compares a track that inherits only with tracks that inherit, and refuses a set
|
||
that mixes declared and inherited namespaces (`staged-malformed`). On a track of a set, `renderGroup`, where present,
|
||
**MUST** be an integer that fits 64 unsigned bits, written as one (SPEC §2), and is
|
||
compared as such: `true`, `1.0` and `"1"` are not `1`. `depends`, where present,
|
||
**MUST** be an array of strings.
|
||
|
||
**MSF fields.** The tracks of a set **MUST** have the same `isLive` (MSF §5.2.7) and
|
||
**MUST** share one `renderGroup` (MSF §5.2.11), which is present on each. On a live
|
||
set, MSF already requires one `targetLatency` across a render group (MSF §5.2.8); it
|
||
is the target latency staged-delivery §8 adds to a unit's timing, unless the sink
|
||
applies its own. Every entry of a track's `depends` (MSF §5.2.14) **MUST** name a track
|
||
of the catalog in the track's own namespace, where MSF resolves it. The `depends` of a
|
||
track of layer *k* **MUST** name at least one track of the set at each layer below
|
||
*k*, and **MUST NOT** name a track of the set at *k* or above; a base track's **MUST
|
||
NOT** name a track of the set. An `altGroup` (MSF §5.2.12) **MAY** group the base
|
||
tracks of alternative sets, and **MUST NOT** be set on a layer track: a subscriber
|
||
picks one track of an alternate group, and a layer is never an alternative to its base
|
||
or to another layer. A layer track **MUST NOT** carry `temporalId` or `spatialId` (MSF
|
||
§5.2.16, §5.2.17), which number a codec's own layers; a layer's index is
|
||
`wai_staged.layer` here, and `refines.layer` in the envelope.
|
||
|
||
**Receipts.** Every track of a set that declares `wai_receipts` true **MUST** carry
|
||
`wai_receipt_alias` (§4) and `wai_receipts_track`: the name, in its namespace, of its
|
||
**receipts track**, of `packaging` `"wai-receipts"`. Each media track has a receipts
|
||
track of its own, so each layer has a receipt chain of its own and a subscriber
|
||
verifies the tracks it subscribes to without the receipts of those it does not
|
||
(jwp-receipts §6.1); and a subscriber subscribes to the receipts tracks of the media
|
||
tracks it subscribes to and no others, so the tracks it chooses cap the receipts it
|
||
receives as they cap its stage. A track **MUST NOT** carry an alias another track of
|
||
the catalog carries, or name a receipts track another track names: a rendition of a
|
||
layer carries an alias and names a receipts track of its own.
|
||
|
||
- Group *g* of a receipts track carries one object: the group's receipts in the binary
|
||
form of jwp-receipts §2.7.3 (`WJR1`), the group receipt and the object receipts of
|
||
group *g* of its media track. A publisher publishes it no later than that group's
|
||
first object. A subscriber reads it by jwp-receipts §2.7.4, and refuses an object
|
||
that does not read (`malformed`) as it refuses one whose receipts do not verify.
|
||
- A receipt is about one MOQT object, and **MUST** say which: the group receipt in
|
||
group *g* of a receipts track has `group_id` *g*, the MOQT Group ID of the media
|
||
track's group, and each object receipt in it has that `group_id` and, as
|
||
`object_id`, the MOQT Object ID of its object. Its `signer_pubkey` is the
|
||
publisher's key and its `track_alias` the media track's `wai_receipt_alias`. A
|
||
subscriber refuses a receipt that breaks this (`moqt_mismatch`, jwp-receipts §3.3)
|
||
before it verifies the receipt's signature, and holds nothing of it.
|
||
- A receipts track has no delivery timeout of either kind, and its publisher priority
|
||
is its media track's. A subscriber sets no delivery timeout on its subscription to a
|
||
receipts track (MOQT §9.20.3, §9.20.4); a receipt it loses anyway it recovers as
|
||
below.
|
||
- Its objects are receipts, not envelopes, and §3 does not apply to them. The
|
||
`WAI_RECEIPT` property (§5) names an object's group receipt but does not carry it.
|
||
|
||
**Verifying, recovering and joining.** A subscriber verifies each group receipt as
|
||
jwp-receipts §3 does, on the chain of the publisher key and the track's
|
||
`wai_receipt_alias`, and admits a group only after its parent (§3 step 3). Each object
|
||
of a receipts track travels on a stream of its own (MSF §6), so receipts can arrive out
|
||
of order, and a relay can drop one at its discretion (MOQT §5.2). It holds no receipt
|
||
of the track's object until it verifies it, and until then applies no layer it carries
|
||
(staged-delivery §10.1).
|
||
|
||
- A subscriber that subscribed to a receipts track from its first group, and that has
|
||
received every object of it, holds the receipt of every object of its media track,
|
||
delivered or not, and checks one it did not receive by inclusion (jwp-receipts §3,
|
||
*Verified by inclusion*). Where a receipts-track object is missing, the receipts after
|
||
it on that track wait for it, within the bound below.
|
||
- A receipt whose parent the subscriber lacks is held (`parent_not_held`). The
|
||
subscriber recovers the parent by a FETCH (MOQT §3.2) of the receipts track's groups
|
||
from its chain's head to the held group: group *g* of the receipts track holds the
|
||
receipts of group *g* of its media track.
|
||
- A subscriber **MUST** state a bound on what it holds for missing parents
|
||
(jwp-receipts §3 step 3, *Holding*), each count a finite number: a count of receipts
|
||
held live for each chain, and a time from each one's arrival only beside it, never
|
||
alone; a count of receipts held as history for each chain; and a count across all its
|
||
chains. Past the live bound it refuses the layers whose receipts it holds, their
|
||
receipts refused (`parent_not_recovered`) and the layers refused for want of them
|
||
(staged-delivery §10.1), and re-anchors the chain at the next validly signed group of
|
||
it that it receives (*Re-anchoring*). The layers of the groups the anchor skips are
|
||
refused for want of their receipts, unless history later fills them.
|
||
- After a re-anchor, a FETCH of the gap (MOQT §3.2) can deliver its groups in any
|
||
order, ascending among them: the verifier admits as history each group that links
|
||
forward from the last group admitted before the gap or back from the anchor
|
||
(jwp-receipts §3 step 3, *History*), never as a fork, and holds as history, under the
|
||
history count, one that links to neither yet.
|
||
- A subscriber that joins a live track after its first group (MOQT §3.5) declares a
|
||
join: its verifier anchors the chain at the first validly signed group of it that it
|
||
receives, and holds the history before it as unverified (jwp-receipts §3 step 3,
|
||
*Joining*). Joining at an earlier group (MOQT §3.5: a SUBSCRIBE whose
|
||
`FILL_PARAMETERS` select it, its objects delivered on a fill fetch stream, §3.4;
|
||
earlier revisions of MOQT called this a Joining FETCH) retrieves earlier receipts.
|
||
The fill fetch stream delivers them in ascending order, so each waits for the one
|
||
after it: the verifier holds them as history, under the history count and outside
|
||
the live one, and admits them, back from the anchor, where their signed links lead to
|
||
it.
|
||
|
||
A MOQT relay forwards a receipts track as it forwards any track, its objects opaque
|
||
payload (MOQT §7.8): it neither reads nor verifies the receipts in it. It is
|
||
therefore not a relay that conforms to jwp-receipts §4, which verifies a group's
|
||
signature before fanning its objects out; the subscriber verifies every receipt itself
|
||
(jwp-receipts §4.1, *Receipts carried as payload*).
|
||
|
||
**Checking a catalog.** A sink checks each set's entries in this order, and stops at
|
||
the first that fails:
|
||
|
||
1. `wai_staged` is an object with the three members above, of their types and in their
|
||
ranges; the track's `packaging` is `"wai"` and its `isLive` a boolean; its name is a
|
||
string, its namespace absent or a string, and the pair is no other track's; its
|
||
`renderGroup`, if present, an integer that fits 64 unsigned bits; its `depends`, if
|
||
present, an array of strings; and, where the checker does not know the catalog
|
||
track's namespace, either every track of the set declares a namespace or none does
|
||
(`staged-malformed`). An entry whose `set` cannot be read belongs to no set, and is
|
||
reported for the empty name.
|
||
2. The set has a track at each layer from 0 to the highest a track of it states
|
||
(`staged-set-incomplete`).
|
||
3. Every entry of every track's `depends` names a track of the catalog in the track's
|
||
namespace; a track of layer *k* names a track of the set at each layer below *k*,
|
||
and none at *k* or above (`staged-depends`).
|
||
4. The tracks have one namespace, one `isLive` and one `renderGroup`, present on each,
|
||
and no layer track carries `altGroup`, `temporalId` or `spatialId`
|
||
(`staged-grouping`).
|
||
5. On a live set, `lead_ms` is an integer on every layer track and `null` on every
|
||
base track (`staged-lead`). A set that is not live is not checked here.
|
||
6. Every `wai_receipt_alias` of the catalog, on any track, is an integer from 0 to
|
||
2^53 − 1; every track of the set that declares `wai_receipts` true carries an alias
|
||
and names, in its namespace, a receipts track of `packaging` `"wai-receipts"`; no
|
||
other track of the catalog carries its alias; and no other track names its receipts
|
||
track (`staged-receipts`).
|
||
|
||
A sink treats the layer tracks of a set that fails as absent. Its base tracks are
|
||
tracks of packaging `wai` like any other, and the sink **MAY** subscribe to them.
|
||
|
||
**Groups and objects.**
|
||
|
||
- Group and object ids of different tracks need not align. `refines` names what a
|
||
layer refines, and a sink checks it (staged-delivery §4.1); nothing is read from
|
||
where a group falls on another track.
|
||
- On a live track each group **MUST** carry exactly one object, which travels in a
|
||
subgroup of its own (MOQT §2.2): MSF maps every object to a new stream (MSF §6), so
|
||
none travels as a datagram. A group of one object is complete when its object is, so
|
||
its group receipt is signed before the object is sent, and the latency the receipt
|
||
adds to a layer's arrival (staged-delivery §8) is one signature and its carriage, not
|
||
a group's duration.
|
||
|
||
**Priority.** MOQT schedules first by subscriber priority, the priority a subscriber
|
||
gives its request (MOQT §5.1.2, §9.20.7), and only between objects of equal subscriber
|
||
priority by publisher priority, the lower number first, 0 the highest (MOQT §5.1.1,
|
||
§5.1.2). The base track's publisher priority **MUST** be a number strictly below every
|
||
layer track's; the priority of a track of layer *k* **MUST** be no greater than that of
|
||
any track of a layer above *k*; and a receipts track's is its media track's. These hold
|
||
for each track's `DEFAULT_PUBLISHER_PRIORITY` (MOQT §10.4) and for every subgroup that
|
||
sets its own (MOQT §5.1.1). A subscriber's own priorities can still put a layer before
|
||
its base: one that wants the publisher's order **SHOULD** give its requests for a set's
|
||
tracks one subscriber priority.
|
||
|
||
**Delivery timeouts.**
|
||
|
||
- A publisher **SHOULD** set a live layer object's `OBJECT_DELIVERY_TIMEOUT` (MOQT
|
||
§5.2, §10.2) to no more than its **slack** at publication: the time from publication
|
||
to the presentation of the last unit the object covers, less the layer's `lead_ms`,
|
||
less the publisher's estimate of the path. MOQT reads a timeout set as an object property on the first
|
||
object of a subgroup (MOQT §5.2, §10.2); here that object is its subgroup's only one,
|
||
so the publisher **MAY** set each object's timeout on the object, from its own slack.
|
||
A slack of zero or less cannot be set, since a timeout of 0 is no timeout (MOQT
|
||
§5.2): such an object cannot arrive in time for any unit it covers, and a publisher
|
||
**MAY** withhold it.
|
||
- A publisher **MUST NOT** set on a base track a delivery timeout of either kind
|
||
shorter than the time from a base's publication to the presentation of its last
|
||
unit.
|
||
- `OBJECT_DELIVERY_TIMEOUT` runs at each hop, from the time the object's last header
|
||
byte reaches that hop, and is checked when the hop passes the object to its transport
|
||
(MOQT §5.2). `SUBGROUP_DELIVERY_TIMEOUT` starts instead when the subgroup's FIN
|
||
arrives, and runs until all its data is acknowledged (MOQT §5.2): on a subgroup of
|
||
one object, it bounds that object's delivery after it was sent. Where both the
|
||
publisher and the subscriber set a value, MOQT uses the smaller (MOQT §5.2, §9.20.3,
|
||
§9.20.4), so a sink whose own target latency is longer than the one a publisher
|
||
assumed cannot extend the publisher's timeout. Either timeout releases, hop by hop,
|
||
bandwidth that a late layer would waste; neither is an end-to-end deadline: an object
|
||
can pass every hop within its timeouts and still arrive late. The rule of
|
||
staged-delivery §8 is the only rule that decides whether a layer is late.
|
||
|
||
**Capping.** A subscriber caps its stage by subscribing to the base tracks and to the
|
||
tracks of layers 1 to *n*, and to their receipts tracks, and to no others. A relay
|
||
**MUST NOT** read an envelope to cap layers: it treats every payload as opaque (MOQT
|
||
§7.8), and forwards the tracks it is asked for. On a track of packaging `wai` the
|
||
prototype header fields of staged-delivery §13 are not read.
|
||
|
||
**Example.** The entry of the first layer track of a live set, with its base track
|
||
`cam1/base`, and the entry of its receipts track:
|
||
|
||
```json
|
||
{ "namespace": "example/cam1", "name": "cam1/L1", "packaging": "wai",
|
||
"isLive": true, "targetLatency": 300, "role": "video", "renderGroup": 1,
|
||
"depends": ["cam1/base"],
|
||
"wai_capabilities": ["wai.video.int_refine"], "wai_determinism": "decode-equivalence",
|
||
"wai_receipts": true, "wai_receipt_alias": 8, "wai_receipts_track": "cam1/L1/receipts",
|
||
"wai_staged": { "set": "cam1", "layer": 1, "lead_ms": 20 } }
|
||
{ "namespace": "example/cam1", "name": "cam1/L1/receipts", "packaging": "wai-receipts",
|
||
"isLive": true }
|
||
```
|
||
|
||
### 4.2 Pinned parameter sets
|
||
|
||
A capability that decodes against a parameter set names that set in each envelope, by a
|
||
pin: the SHA-256 of the whole set, in a declared form, with the tier the sender claims
|
||
(SPEC §3.1). A track **MAY** also state, in its catalog entry, the pins its objects carry.
|
||
A sink can then tell before it subscribes whether it holds the parameter sets the track
|
||
needs, and which tier each is offered at:
|
||
|
||
```json
|
||
"wai_priors": [
|
||
{ "capability": "wai.neural.int_hyper",
|
||
"sha256": "<64 lowercase hexadecimal digits>",
|
||
"form": "wai.prior.set", "tier": "decode-equivalence" }
|
||
]
|
||
```
|
||
|
||
| member | presence | meaning |
|
||
|---|---|---|
|
||
| `capability` | REQUIRED, string | A capability the track's `wai_capabilities` lists. |
|
||
| `sha256` | REQUIRED, string | A pin's `sha256`: 64 lowercase hexadecimal digits. |
|
||
| `form` | OPTIONAL, string | The pin's `form`. Absent, or `null`, where the pin declares none. |
|
||
| `tier` | OPTIONAL, string | The tier the publisher offers the set at, from SPEC §3.1's vocabulary. Advisory (below). |
|
||
|
||
An entry **lists** a pin for a capability when its `capability` is that capability, its
|
||
`sha256` is the pin's, and its `form` is the pin's: both absent, or the same string. A
|
||
member of an entry that this revision does not define is ignored.
|
||
|
||
- **Reading.** `wai_priors`, where present and not `null`, is an array of objects, each
|
||
with the members above of their types and no two with the same `capability`, `sha256`
|
||
and `form`, and each `capability` one the track's `wai_capabilities` lists. A sink that
|
||
reads anything else refuses the track (`catalog_priors_malformed`): it does not
|
||
subscribe to it. A `tier` this revision does not register is read as `none` (SPEC §3.1).
|
||
- **Exhaustive.** Where a track carries `wai_priors`, the publisher **MUST** list in it
|
||
every pin its objects carry. An object **breaks the catalog** when:
|
||
1. it carries a pin that no entry lists, either for its `capability` or for its
|
||
`fallback`; or
|
||
2. an entry names its `capability`, or its `fallback` (not `null`), and the object
|
||
carries no pin, or carries a pin that no entry lists for that capability.
|
||
|
||
A publisher whose objects' pin can decode through the fallback (SPEC §4 step 5)
|
||
**SHOULD** list the set for the fallback as well, so that a sink that holds only the
|
||
fallback can make the same decision before it subscribes.
|
||
- **Mismatch.** A sink checks each object of such a track against the track's
|
||
`wai_priors` before it dispatches the object (SPEC §4). An object that breaks the
|
||
catalog is refused (`catalog_prior_mismatch`). The track is refused with it: the sink
|
||
dispatches no further object of the track, and **MAY** unsubscribe from it. The
|
||
catalog never stands in for the envelope. A sink verifies an object's pin under SPEC
|
||
§3.1 whether or not the catalog lists it. An entry never satisfies a pin, never
|
||
supplies a pin the envelope lacks, and never changes which set a decode reads.
|
||
- **Tier.** A listed `tier` is advisory. A publisher **MUST NOT** list a tier above the
|
||
one the capability's registry class gives (SPEC §3.1). A sink **MUST NOT** claim a tier
|
||
on the strength of a listed tier: the tier it claims for an object is the one its own
|
||
decode earns, under SPEC §3.1 and §7 and [determinism-tiers](determinism-tiers.md) §3,
|
||
whatever the catalog says. A listed tier that differs from the tier an object's pin
|
||
states is not a mismatch.
|
||
- `wai_determinism` (§4) still states the weakest tier any object on the track claims.
|
||
|
||
Reference implementation: `staged::moq::CatalogPriors` reads the member
|
||
(`CatalogPriors::read`) and checks an object against it (`CatalogPriors::check`,
|
||
`CatalogPriors::check_manifest`). `staged-conformance/moq/priors.json` holds a case for
|
||
each reason and for each way an object can break the catalog. `wai_staged_vectors`
|
||
checks the cases, and so does `staged_verify.py`, which shares no code with `wai-rs`.
|
||
The browser sink of `joule-moq-web` dispatches no capability that decodes against a
|
||
parameter set, and does not read the member.
|
||
|
||
## 5. The per-object property
|
||
|
||
MOQT carries metadata a relay can see as **Properties** of a track or of an object
|
||
(MOQT §8.4), registered in a table MOQT creates, with ranges set aside for an
|
||
application's own use. MSF §12 defines its properties this way; the one it defines,
|
||
`MSF_COMPRESSION`, is at both scopes. CMSF adds track-level catalog fields and
|
||
defines **no per-object property**. None of them carries a receipt.
|
||
|
||
A WAI receipt is already per-object. This extension therefore defines one
|
||
object-scope property, `WAI_RECEIPT`, whose value is the object's receipt
|
||
commitment:
|
||
|
||
| field | type | meaning |
|
||
|---|---|---|
|
||
| `content_hash` | 32 B | `jwp-receipts` §2.1 BLAKE3 over the envelope bytes. |
|
||
| `joules_micro` | u64 | Microjoules producing this object; `0` is the unmetered marker, never 0 J ([energy-measurement](energy-measurement.md) §2). |
|
||
| `acquisition` | string | Acquisition class per [energy-measurement](energy-measurement.md) §2; empty when `joules_micro` is `0`. |
|
||
| `acquisition_evidence` | bytes | For `OnChipCounter` and `CalibratedInstrument`, the evidence that follows the tag byte in `energy-measurement.md` §2.7 (declared uncertainty, then filtering state or calibration reference). Empty for every other class. |
|
||
| `group_receipt` | 32 B | The link hash of the group receipt covering this object (`jwp-receipts` §3 step 3). |
|
||
|
||
On the wire the property is one Key-Value-Pair (MOQT §8.3) of an odd type. Its value is
|
||
the five fields in the order of the table, with nothing after them: `content_hash` (32
|
||
bytes); `joules_micro` (a MOQT variable-length integer, MOQT §8.1); `acquisition` (a
|
||
variable-length integer giving its length, then its UTF-8 bytes); `acquisition_evidence`
|
||
(a variable-length integer giving its length, then the bytes); and `group_receipt` (32
|
||
bytes). A sink refuses a value that ends early, has bytes left after `group_receipt`, or
|
||
whose `acquisition` is not UTF-8 (`property_invalid`). The publisher sets the property
|
||
inside the object's Immutable Properties (MOQT §10.7), which no relay may change or
|
||
remove. No property type is registered for it. The Internet-Draft source in `wai/ietf/`
|
||
requests one from the Specification Required range of MOQT's property registry (MOQT
|
||
§16.8). Until one is assigned, a track of packaging `wai` carries the property as type
|
||
`0x3A41`, from the range MOQT §8.4 sets aside for an application's own use. Another
|
||
application may give that type another meaning, so a sink reads it only on a track of
|
||
packaging `wai`.
|
||
|
||
- A non-zero `joules_micro` **MUST** be accompanied by `acquisition`. A joule
|
||
figure without its acquisition class is exactly what `energy-measurement.md`
|
||
§2 forbids, and putting it on the wire does not make it acceptable.
|
||
- A zero `joules_micro` is the unmetered marker: `acquisition` **MUST** be
|
||
empty and `acquisition_evidence` empty, since a class beside a zero figure
|
||
claims a measurement that did not occur (`energy-measurement.md` §2). A sink
|
||
**MUST NOT** present the marker as a figure of zero joules, and **MUST**
|
||
discard a property that breaks this bullet or the one above
|
||
(`property_invalid`).
|
||
- `acquisition_evidence` **MUST** be present and non-empty for a refined
|
||
hardware class (`OnChipCounter`, `CalibratedInstrument`), and empty
|
||
otherwise — including for legacy `HwShunt`, which carries no evidence. A
|
||
refined class without its declared uncertainty is refused by
|
||
`energy-measurement.md` §2 on the same terms as a figure without a class. A
|
||
property written before this field existed has no `acquisition_evidence`; a
|
||
reader treats its absence as empty, which is valid only for the classes that
|
||
carry none.
|
||
- No signature covers this property. A sink **MUST NOT** report `acquisition`
|
||
or `acquisition_evidence` as signed unless they and `joules_micro` equal a
|
||
signed statement it holds of the same object, which is one of:
|
||
- the label in the object's Merkle leaf, once the object receipt has been
|
||
accepted under `jwp-receipts` §3 against the group receipt
|
||
`group_receipt` names — `group_receipt` must then name that group; or
|
||
- an `energy` claim about the object that it has accepted under
|
||
`jwp-receipts` §3.1. A claim names no group, so `group_receipt` is outside
|
||
what it states.
|
||
|
||
Where such a statement gives another figure, class, evidence or group, the
|
||
sink **MUST** discard the property (`property_disagrees`). Where it holds
|
||
none, the class is outside every signature, and the sink reads the figure as
|
||
unlabelled.
|
||
- A class read as signed is the statement of the key that signed it: the group
|
||
receipt's `signer_pubkey` for a leaf, the claim's `signer_pubkey` for an
|
||
`energy` claim. A sink **MUST** report that key with the class, and **MUST
|
||
NOT** report a class a claim signs as the publisher's unless the claim's
|
||
signer is a key it holds as the publisher's. Anyone can sign an `energy`
|
||
claim, a relay that rewrites this property included.
|
||
- A relay **MUST** preserve this property unchanged, per `jwp-receipts` §4.1.
|
||
A relay that recomputes it is asserting something it did not measure.
|
||
- A relay that does not forward an object, whether because a delivery timeout
|
||
passed or at its discretion (MOQT §5.2), **MUST** still forward that object's
|
||
receipts to every subscriber that requested receipts for its track (jwp-receipts
|
||
§4.1). This property rides on the object and is lost with it; the receipts ride
|
||
the track's receipts track (§4.1), which the relay forwards as it forwards any
|
||
track, so a subscriber of that receipts track that receives the receipt accounts
|
||
for the object as `inclusion-verified` (jwp-receipts §4.4). A receipts-track object
|
||
the relay drops in turn is recovered as §4.1 says. A relay that signs a `delivery-gap` claim (jwp-receipts
|
||
§2.4.11) does not add it to the publisher's receipts track, whose objects it
|
||
forwards unaltered (MOQT §7.8); this extension defines no carriage for such a
|
||
claim.
|
||
- Telemetry about objects on a WAI track is
|
||
[delivery-telemetry](delivery-telemetry.md)'s. It is unsigned and advisory, and it
|
||
**MUST NOT** restate this property's figure, class or evidence.
|
||
- Absence of the property is not evidence of absence of a receipt; receipts MAY
|
||
ride elsewhere (§1 of `jwp-receipts` lists sidecar and trailer carriage). A
|
||
sink **MUST NOT** infer that an object without the property is unattested.
|
||
|
||
## 6. What this extension does not do
|
||
|
||
Stated so it is not re-argued:
|
||
|
||
- It does **not** define, profile, or restate MOQT's wire encoding — not the
|
||
object header, not the byte layout of properties, not the control messages.
|
||
Those are MOQT's, and the property that makes this binding cheap is that WAI
|
||
rides a transport instead of defining one. §4.1 paraphrases MOQT's scheduling
|
||
order, its delivery timeouts and its joining (MOQT §5.1, §5.2, §3.5) only to say how
|
||
a stage set uses them and which values it sets; it defines none of them, and where a
|
||
paraphrase and MOQT differ, MOQT holds.
|
||
- It does **not** replace CMSF or LOC. A publisher with CMAF-packaged media
|
||
should use CMSF; this packaging is for tracks whose objects are WAI envelopes.
|
||
- It does **not** make MoQ WAI's transport. WAI remains carrier-agnostic; this
|
||
is one binding among the several `jwp-receipts` §1 anticipates.
|
||
- It is not the only binding. [http-delivery](http-delivery.md) binds the same
|
||
objects to HTTP, admits `WAI2` envelopes with verified component ranges, carries
|
||
`WAI_RECEIPT` in a sidecar index, and maps this extension in its Appendix C.
|
||
- It does **not** pin a MOQT draft revision. MOQT is pre-Last-Call; an extension
|
||
that pinned a moving target would be wrong within a meeting cycle. Where MOQT
|
||
and this text disagree, MOQT wins and this text is the thing that must change.
|
||
The revisions named in the status note are the ones this text was last read
|
||
against: where a later revision renumbers or renames what it cites, the
|
||
citation is what changes.
|
||
- Nothing in it depends on which MOQT draft carries the objects: an envelope, its
|
||
`WAI_RECEIPT` property inside Immutable Properties, a receipts track and its
|
||
catalog fields are the same on every draft that has Immutable Properties. The
|
||
reference implementation (`joule-moq` and `joule-moq-relay`, in
|
||
`joule-code/joule-code-rs/crates/`) speaks `draft-ietf-moq-transport-22`, `-18`
|
||
and `-16`, the draft chosen per session by ALPN or `WT-Available-Protocols`, and
|
||
carries this extension's objects and receipts unchanged on each. Its README
|
||
records the relays and drafts it has carried them through, and when.
|
||
|
||
## 7. Framing
|
||
|
||
The delivery industry is converging on MoQ, and the layer above it is where
|
||
formats declare themselves. A container that carries instructions, dispatches by
|
||
capability, and commits per object has a natural home there — not because it
|
||
competes with the transport, but because the transport deliberately left the
|
||
question of meaning to someone else.
|