open-standards/wai/extensions/moq-streaming-format.md
Transaction Science 1fa6a0ba8a
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
Merge origin/main into joule-moq/multi-draft
wai/CHANGELOG.md: both entries kept, this branch's above main's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 19:04:02 -04:00

36 KiB
Raw Permalink Blame History

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), 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 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. 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 §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 §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) 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,

"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:

{ "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:

"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 §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 §2).
acquisition string Acquisition class per energy-measurement §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'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 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.