Two claim classes report on one energy figure without changing the receipt that seals it. `measurement` carries a bracket's values as integers with each bracket's liveness facts, the build and the power reader, and is accepted only against the receipt that carries its figure. `operating-point` records the requested, committed and delivered operating points and a prediction. The ratio, verdict and residual are derived by every reader, never stated. energy-measurement §6.2 gives the figure and the least declaration a report's integers support as exact integer formulas; wai-rs `energy_report` implements them with no floating-point arithmetic. §3.1 defines a live power reader: a rail must advance in two consecutive intervals, in at least five and in at least four of every five, over at least 300 ms. A frozen or held rail is refused rather than sealed under a hardware class. An unmetered `0` is never a figure of zero joules. A JWP object's figure, class and origin are bound into its Merkle leaf (`\x02`), and a labelled group payload signs the total's class, its coverage and the parent link. An object must sit at the position its inclusion proof walks, and group signatures follow the strict Ed25519 rule claims use. `jwp_delivery` and `energy_figure` implement this in wai-rs; joule-moq 0.2.0 builds and verifies both labelled forms. New corpus `jwp-conformance/` with `jwp_verify.py`, more claim vectors with the earlier ones pinned, and a CI job that runs claim_verify.py, differential.py and jwp_verify.py. Legacy receipts keep their bytes. External JWP implementations must adopt the `\x02` leaf and the labelled group payload before verifying objects or groups that use them. Co-Authored-By: Claude Opus 5.5 <[email protected]>
174 lines
9.7 KiB
Markdown
174 lines
9.7 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.
|
|
|
|
## 1. Why a streaming format
|
|
|
|
MoQ separates two things that other stacks fuse:
|
|
|
|
- **MOQT** (`draft-ietf-moq-transport`) moves objects, in groups, on tracks. It
|
|
does not know what an object means.
|
|
- **MSF** (`draft-ietf-moq-msf`) says what objects mean: it fragments a stream
|
|
into objects, declares what a publisher emits in a **catalog**, and sets
|
|
prioritisation.
|
|
|
|
WAI already references MoQ for confidentiality (MLS + SFrame; see
|
|
[confidentiality](confidentiality.md)). That binding treats MoQ as a pipe. This
|
|
one treats it 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 (`loc`, `mediatimeline`, `eventtimeline`, `moqlog`, `moqmetrics`,
|
|
`catalog`). 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_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.
|
|
|
|
These are custom catalog fields under MSF's extension rule; they are namespaced
|
|
by their `wai_` prefix and MUST NOT collide with MSF-defined names.
|
|
|
|
## 5. The per-object property
|
|
|
|
MSF §12 defines **MSF Properties** at **Track** and **Object** scope, with an
|
|
IANA registry of property types that today holds essentially one entry
|
|
(`MSF_COMPRESSION`). CMSF adds track-level catalog fields and defines **no
|
|
per-object property**. The object-scope slot is empty.
|
|
|
|
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). |
|
|
|
|
- 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.
|
|
- 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 extension-header byte layout, 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.
|
|
- 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 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.
|
|
|
|
## 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.
|