extensions/loc-carriage.md maps an envelope whose payload is one WebCodecs chunk (an Opus packet, an AVC or HEVC access unit in Annex B, an ADTS frame) onto one object of a track of packaging loc (draft-ietf-moq-loc-04): the payload as the LOC Payload, the manifest as the Immutable Property WAI_MANIFEST (0x3A61), the receipt commitment as WAI_RECEIPT. A reader rebuilds the envelope byte for byte and checks the receipt against it, refusing with the extension's codes. joule-moq's loc module writes and reads; its example writes the corpus and tests/loc_vectors.rs checks it; loc_verify.py is the second implementation. WAI_MANIFEST is registered as a wire identifier; moq-streaming-format §6 points to the carriage. Co-Authored-By: Claude Opus 5.5 <[email protected]>
36 KiB
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) anddraft-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 (
WAI1magic + 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_aliasnames the track's receipt chain, which a verifier keeps per publisher key andtrack_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: not8.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, andwai_receipts_track, on the tracks of a stage set that declare receipts; on another track both are OPTIONAL.wai_capabilitiesMUST 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_determinismis 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_usagelets 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_idg, the MOQT Group ID of the media track's group, and each object receipt in it has thatgroup_idand, asobject_id, the MOQT Object ID of its object. Itssigner_pubkeyis the publisher's key and itstrack_aliasthe media track'swai_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_RECEIPTproperty (§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_PARAMETERSselect 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:
wai_stagedis an object with the three members above, of their types and in their ranges; the track'spackagingis"wai"and itsisLivea boolean; its name is a string, its namespace absent or a string, and the pair is no other track's; itsrenderGroup, if present, an integer that fits 64 unsigned bits; itsdepends, 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 whosesetcannot be read belongs to no set, and is reported for the empty name.- The set has a track at each layer from 0 to the highest a track of it states
(
staged-set-incomplete). - Every entry of every track's
dependsnames 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). - The tracks have one namespace, one
isLiveand onerenderGroup, present on each, and no layer track carriesaltGroup,temporalIdorspatialId(staged-grouping). - On a live set,
lead_msis an integer on every layer track andnullon every base track (staged-lead). A set that is not live is not checked here. - Every
wai_receipt_aliasof the catalog, on any track, is an integer from 0 to 2^53 − 1; every track of the set that declareswai_receiptstrue carries an alias and names, in its namespace, a receipts track ofpackaging"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.
refinesnames 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'slead_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_TIMEOUTruns 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_TIMEOUTstarts 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 notnull, is an array of objects, each with the members above of their types and no two with the samecapability,sha256andform, and eachcapabilityone the track'swai_capabilitieslists. A sink that reads anything else refuses the track (catalog_priors_malformed): it does not subscribe to it. Atierthis revision does not register is read asnone(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:- it carries a pin that no entry lists, either for its
capabilityor for itsfallback; or - an entry names its
capability, or itsfallback(notnull), 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.
- it carries a pin that no entry lists, either for its
-
Mismatch. A sink checks each object of such a track against the track's
wai_priorsbefore 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
tieris 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_microMUST be accompanied byacquisition. A joule figure without its acquisition class is exactly whatenergy-measurement.md§2 forbids, and putting it on the wire does not make it acceptable. -
A zero
joules_microis the unmetered marker:acquisitionMUST be empty andacquisition_evidenceempty, 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_evidenceMUST be present and non-empty for a refined hardware class (OnChipCounter,CalibratedInstrument), and empty otherwise — including for legacyHwShunt, which carries no evidence. A refined class without its declared uncertainty is refused byenergy-measurement.md§2 on the same terms as a figure without a class. A property written before this field existed has noacquisition_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
acquisitionoracquisition_evidenceas signed unless they andjoules_microequal 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 receiptgroup_receiptnames —group_receiptmust then name that group; or - an
energyclaim about the object that it has accepted underjwp-receipts§3.1. A claim names no group, sogroup_receiptis 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. - the label in the object's Merkle leaf, once the object receipt has been
accepted under
-
A class read as signed is the statement of the key that signed it: the group receipt's
signer_pubkeyfor a leaf, the claim'ssigner_pubkeyfor anenergyclaim. 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 anenergyclaim, 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 adelivery-gapclaim (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-receiptslists 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.
An envelope whose payload is one chunk of a WebCodecs codec may instead travel on
a track of packaging
loc, its manifest and receipt in properties of the object (loc-carriage). - 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
WAI2envelopes with verified component ranges, carriesWAI_RECEIPTin 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_RECEIPTproperty inside Immutable Properties, a receipts track and its catalog fields are the same on every draft that has Immutable Properties. The reference implementation (joule-moqandjoule-moq-relay, injoule-code/joule-code-rs/crates/) speaksdraft-ietf-moq-transport-22,-18and-16, the draft chosen per session by ALPN orWT-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.