extensions/telemetry-aggregation.md: four measures over session metrics (Prio3SumVec, Prio3Sum, Prio3Histogram), the DAP task's task_info layout and id, the floor of fifty reports each aggregator enforces, the WQS1 statement each aggregator signs, the WQA1 receipt the collector signs, verification, and a room's cell. delivery-telemetry: a class and what it reveals for every CMCD key the mapping writes (§3.4), delivery-only Request mode (§4.3), §8.1 stated as facts with no consent gate, §8.4 pointing to aggregation. live-metering §4.7/§6.5: a room may aggregate its QoE instead of sending logs. wai-rs: HPKE base mode (RFC 9180 A.1.1), DAP-20 messages, a reference client, leader, helper and collector, the receipt verifier, and the corpus generator; live-conformance/aggregate/vectors.json from fixed seeds. Co-Authored-By: Claude Opus 5.5 <[email protected]>
21 KiB
WAI Extension: Delivery Telemetry
Status: Draft. An unsigned, advisory observation about WAI delivery that a sink, edge or origin emits; its mapping onto the keys of CMCD version 2 (CTA-5004-B) and CMSD (CTA-5006), written as RFC 9651 Structured Field Values, with each key's class and what it reveals (§3.4); and the session metrics a log of observations gives, which telemetry-aggregation aggregates without the service holding any session's. Reference impl:
wai-rs—telemetry::{sf, observation, cmcd, cmsd, qoe}; thewai_telemetrytool; the corpustelemetry-conformance/, with a differential against an independent open-source CMCD and CMSD library. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.
1. Status of an observation
§1.1 No signature covers an observation. A relying party MUST NOT treat one as evidence of delivery, presentation, quality or energy. What a session did is attested, where it is, by receipts and claims (jwp-receipts), never by telemetry.
§1.2 An observation MUST NOT carry a joule figure, an acquisition class or a quality figure. It MAY carry content hashes and link hashes as pointers.
§1.3 Telemetry binds no receipt to CMCD or CMSD, and no receipt to telemetry.
2. The observation
§2.1 An observation is a JSON object with exactly these members; a member that does not
apply is null. Integers are unsigned and at most 2⁵³ − 1, and a member a report
carries as an Integer (§3.3: sn, ts_ms, the object's d_ms, dl_ms, ttfb_ms,
ttlb_ms and rc, and the metrics) at most 10¹⁵ − 1, the largest RFC 9651 Integer, so
every observation can be reported. A string a report carries is printable ASCII.
| member | type | meaning |
|---|---|---|
v |
integer | 1 |
sid |
string | 32 lowercase hexadecimal digits, fresh per session, derived from no identifier (§8) |
sn |
integer | the observation's sequence number in the session |
ts_ms |
integer or null |
Unix time of the event in milliseconds; required for every event reported in Event mode (§3.1) |
event |
string | request, response, start, present, switch, layer-change, stall-start, stall-end, frame-drop, capable, abandon or end |
cid |
string or null |
the entry object's content hash (64 lowercase hexadecimal digits), or a deployer's content id of 1–64 letters, digits, ., _ and - |
st |
string | stream type: v (on demand), l (live) or ll (low-latency live) |
sf |
string | o for WAI objects over the HTTP binding; h or d for a WAI rendition referenced from HLS or DASH |
object |
object or null |
for request and response: the object (§2.2) |
present |
object or null |
for present: { unit, layers, first, late_ms, decode_us }; first is true only for the session's first presentation; layers is at least 1 |
switch |
object or null |
for switch: { unit, from, to, reason }; from and to are primary or fallback and differ; reason is a §5 term |
layer_change |
object or null |
for layer-change: { from, to }, a change of layer_cap; null is no cap |
capable |
object or null |
for capable: { outcome, reason, classical_floor } (§2.3) |
cohort |
string or null |
control or wai: the arm the content's service assigned the session to |
metrics |
object or null |
{ msd_ms, bsa, bsda_ms, dfa }: media start delay, buffer starvations and their total duration since the session began, and dropped frames since the session began |
claim |
64 hexadecimal digits or null |
on end only: the link hash of the session's reconstruct-session claim (jwp-receipts §2.4.12), where the sink sealed one |
§2.2 The object has exactly the members ot, url, content_hash, capability,
rendition, layer, layers_total, layer_cap, counted, transferred, d_ms,
dl_ms, ttfb_ms, ttlb_ms, rc, path and receipt, each null where it does
not apply:
otis a CTA-5004-B object type token (m,a,v,av,i,c,tt,k,o);content_hashthe object's content hash andreceipta receipt's link hash, each 64 lowercase hexadecimal digits;capabilityat most 64 characters;renditiontheWAI2rendition index;layerandlayers_totalthe object's stage and its stage set's stage count;layer_capthe cap the request asks for;countedits counted bytes andtransferredits transferred bytes (staged-measurement §3);pathprimaryorfallback;urlthe object's address, at most 2048 characters, holding no?,#,@or\(§8.2).- The request keys
ot,d_ms,dl_msandlayer_caparenullon aresponse; the response keysrc,ttfb_ms,ttlb_msandurlarenullon arequest.
§2.3 A capable observation reports one WAI-arm dispatch outcome: not-reached
(no dispatch decision for a WAI object was reached), missing-capability,
pin-refused, module-refused, below-rate or capable. reason is SPEC §3.1's pin
refusal for pin-refused (one of the terms §5 lists before missing-capability), and
null otherwise. classical_floor is the platform's answer for the classical
rendition's codec string, supported or unsupported, where the sink asked. The
outcome comes from WAI's own dispatch: advertised capabilities, pin verification and a
timed decode. A sink MUST NOT derive it from a platform media-capability query,
which answers only for the platform's own decoders; it reports that answer separately,
as classical_floor. How sessions are assigned to arms, and the shares computed over
outcomes, are not specified in this revision.
§2.4 An observation that breaks §2.1–§2.3 is refused as a whole: a member missing or not listed, a value of another type or outside its range, an object on an event that has none, or none on one that has one.
3. CMCD version 2 mapping
§3.1 The modes:
- a
requestobservation is reported in Request mode; - every other observation is reported in Event mode, with
ts: aresponseas an event report withe=rr, whose response keys (rc,ttfb,ttlb,url) are never carried in Request mode or in a header shard; - a
presentobservation withfirst: falseis not reported in CMCD.
Every report carries v=2, sid, sn, st and sf, and cid where it is not null.
| observation | CMCD | mode |
|---|---|---|
sid, sn, cid, st, sf |
same keys | every report |
object.ot, object.d_ms, object.dl_ms |
ot, d, dl |
request |
object.rc, object.ttfb_ms, object.ttlb_ms, object.url |
rc, ttfb, ttlb, url, with e=rr |
event |
ts_ms |
ts |
event |
start / end / abandon |
e=ps, sta=s / e / q |
event |
present with first: true |
e=ps, sta=p |
event |
stall-start / stall-end |
e=ps, sta=r / p |
event |
metrics.msd_ms, metrics.dfa |
msd, dfa |
any report |
metrics.bsa, metrics.bsda_ms |
bsa, bsda, each an inner list of one Integer |
any report |
switch, capable, layer-change, frame-drop |
e=ce, cen = "wai-switch", "wai-capable", "wai-layer-change", "wai-frame-drop" |
event |
§3.2 An emitter MUST NOT put counted bytes, a layer or a capability into br, tb,
pb or any other bitrate key.
§3.3 A report is an RFC 9651 Dictionary: Integers for counts and times, Tokens for
st, sf, ot, e and sta, Strings for sid, cid, url and cen. Its keys are
written in bytewise order and without optional whitespace, and a key is written only
when its value is not null. The corpus fixes the output byte for byte.
§3.4 Key classes. Each key this mapping writes has a class:
delivery: what the host serving the request needs to serve it;measurement: what is only aggregated afterwards.
A class informs what a deployment does with a key (§4.3, telemetry-aggregation §9); it is not a permission, and no key is conditional on anything but the observation's members. A Request-mode report goes to the host that serves the object, an Event-mode report to the content's own service (§8.3).
| key | class | what it reveals | reported in |
|---|---|---|---|
v |
delivery | the CMCD version the report is written in | every report |
st |
delivery | whether the session plays on demand, live or low-latency live, which a host may serve differently | every report |
ot |
delivery | the kind of object requested, which a host may schedule or cache by | Request mode |
d |
delivery | the requested object's playback duration, which a host may prefetch by | Request mode |
dl |
delivery | the time until the sink's buffer runs out: its buffer level | Request mode |
science.transaction.wai-ch |
delivery | the exact object, by content hash, which a content-addressed host serves by | Request mode, e=rr |
science.transaction.wai-lc |
delivery | the layer cap the request asks the host to apply: the sink's quality ceiling | Request mode, wai-layer-change |
sid |
measurement | that reports belong to one session; it is fresh per session (§8.1), so nothing across sessions | every report |
sn |
measurement | the report's place in its session, so how many came before it | every report |
cid |
measurement | the content the session plays | every report |
sf |
measurement | the delivery format: WAI objects, or a WAI rendition through HLS or DASH | every report |
msd |
measurement | the session's media start delay | any report |
bsa, bsda |
measurement | the session's buffer starvations and their duration so far: its network conditions | any report |
dfa |
measurement | the frames dropped so far: the device's decode performance | any report |
ts |
measurement | when the event happened | Event mode |
e, sta |
measurement | the event and the player's state | Event mode |
rc, ttfb, ttlb |
measurement | a response's status and timing: the sink's throughput and network latency | Event mode |
url |
measurement | the address of a response's object | Event mode |
cen |
measurement | which of this mapping's custom events occurred | Event mode |
science.transaction.wai-b, science.transaction.wai-tx |
measurement | the bytes counted and transferred for an object | Request mode, e=rr |
science.transaction.wai-cap |
measurement | a capability the sink decodes: device-identifying | Request mode, e=rr |
science.transaction.wai-ri |
measurement | the rendition chosen: the sink's bandwidth and device class | Request mode, e=rr |
science.transaction.wai-l, science.transaction.wai-lt |
measurement | how far into a stage set the sink fetches | Request mode, e=rr |
science.transaction.wai-p |
measurement | whether the sink fell back to the classical rendition | Request mode, e=rr, wai-switch |
science.transaction.wai-rcpt |
measurement | a receipt the sink holds, which links the request to its receipt chain | Request mode, e=rr |
science.transaction.wai-lcf |
measurement | the previous layer cap | wai-layer-change |
science.transaction.wai-coh |
measurement | the experiment arm of the session | every report |
science.transaction.wai-u |
measurement | a unit presented or switched | sta=p, wai-switch |
science.transaction.wai-pl, science.transaction.wai-late, science.transaction.wai-dec |
measurement | the layers presented, lateness and decode time: device performance, device-identifying | sta=p |
science.transaction.wai-sw |
measurement | why the sink switched | wai-switch |
science.transaction.wai-co, science.transaction.wai-cr, science.transaction.wai-cf |
measurement | the dispatch outcome and the platform's classical-floor answer: device-identifying | wai-capable |
science.transaction.wai-clm |
measurement | the session's sealed claim, which links the session to its signed claim | sta=e |
A key this mapping does not write has no class here.
4. Custom keys
§4.1 Custom keys have the form science.transaction.wai-<x>: a lowercase first letter,
then lowercase letters, digits, . and -, with an inner hyphen (CTA-5004-B's
custom-key rule). Every custom key's value is a String, the type CTA-5004-B admits for
custom keys besides a Token; an integer member is written as its decimal digits.
| key | from | report | header shard (Request mode) |
|---|---|---|---|
science.transaction.wai-b |
object.counted |
request, e=rr |
CMCD-Object |
science.transaction.wai-tx |
object.transferred |
request, e=rr |
CMCD-Object |
science.transaction.wai-ch |
object.content_hash |
request, e=rr |
CMCD-Object |
science.transaction.wai-cap |
object.capability |
request, e=rr |
CMCD-Object |
science.transaction.wai-ri |
object.rendition |
request, e=rr |
CMCD-Object |
science.transaction.wai-l |
object.layer |
request, e=rr |
CMCD-Object |
science.transaction.wai-lt |
object.layers_total |
request, e=rr |
CMCD-Object |
science.transaction.wai-p |
object.path, or switch.to: "p" or "f" |
request, e=rr, wai-switch |
CMCD-Object |
science.transaction.wai-rcpt |
object.receipt |
request, e=rr |
CMCD-Object |
science.transaction.wai-lc |
object.layer_cap, or layer_change.to |
request, wai-layer-change |
CMCD-Request |
science.transaction.wai-lcf |
layer_change.from |
wai-layer-change |
— |
science.transaction.wai-coh |
cohort |
every report | CMCD-Session |
science.transaction.wai-u |
present.unit, or switch.unit |
sta=p (first presentation), wai-switch |
— |
science.transaction.wai-pl, -late, -dec |
present.layers, present.late_ms, present.decode_us |
sta=p (first presentation) |
— |
science.transaction.wai-sw |
switch.reason |
wai-switch |
— |
science.transaction.wai-co, -cr, -cf |
capable.outcome, capable.reason, capable.classical_floor |
wai-capable |
— |
science.transaction.wai-clm |
claim |
sta=e |
— |
§4.2 In Request mode the report also goes in header shards: each key to the shard
CTA-5004-B assigns it (v, sid, cid, st, sf and msd to CMCD-Session; sn,
dl and dfa to CMCD-Request; ot and d to CMCD-Object; bsa and bsda to
CMCD-Status), and each custom key to the shard of §4.1. Each shard is a Dictionary
written as §3.3 writes a report; a shard with no key is not sent.
§4.3 Delivery-only Request mode. A sink MAY report Request mode delivery-only: a
Request-mode report then carries the members of its observation whose keys are of the
delivery class (§3.4) and no other, written as §3.3 writes a report, in the header shards of
§4.2 when it goes in shards. The measurement-class members are not sent to the host; the
sink reports them to the content's own service in Event mode, or as the measures of
telemetry-aggregation §3. A reader that checks a report it is
told is delivery-only refuses one that carries a key not of the delivery class,
measurement_key_in_delivery, naming the first such key in bytewise order.
5. Switch reasons
§5.1 A switch reason is one of:
- SPEC §3.1's pin refusals, in the order of its checks:
pin-malformed,form-not-registered,form-unreadable,form-required,prior-revoked,prior-not-authorised(check 5, at a sink that holds a key log),prior-not-held,digest-superseded,set-malformed,part-mismatch,set-incomplete; missing-capability;module-unverified,module-revoked,trap,fuel-exhausted,memory-exhausted,deadline-threshold,unit-hash-mismatch,budget-exhausted;recovered.
A later extension adds its reasons here.
6. CMSD mapping
§6.1 An origin or edge MAY describe a WAI object in CMSD-Static with at, d,
ht, nor, nrr, ot, sf, st and su, and the custom keys
science.transaction.wai-ch (its content hash), -l and -lt (its layer and its
stage set's stage count) and -rcpt (a delivery receipt's link hash), each a String.
v is not written for version 1.
§6.2 A deriving node adds science.transaction.wai-op, its operation token, as a Token.
§6.3 CMSD-Dynamic MAY state, for each node, etp, rtt, mb, rd and du, and
science.transaction.wai-drop (a String): the number of objects the node discarded on
delivery timeout since its previous response to the same session. Each node adds its
own entry, in order; within an entry the parameters are in bytewise order.
§6.4 Keys and parameters are written as §3.3 writes a report: in bytewise order, a key
only when its value is not null, and a Boolean (su, du) only when true.
§6.5 CMSD carries no joule figure. An edge's energy travels in a claim.
7. Session metrics
§7.1 A log is one session's observations in sn order: one sid, sn strictly
increasing, timestamps never decreasing, and nothing after end or abandon.
§7.2 A log gives, in integer milliseconds and bytes:
started: the log has astart;vst_ms, the video start time: the first presentation'sts_msless thestart's,nullwithout both;exit_before_start: anabandonbefore any first presentation;start_failed: a session that started, never presented, and ended withend;stallsandstall_ms: the stalls begun after the first presentation, each from itsstall-startto itsstall-end, or toendorabandonwhere it is still open, and their total duration;engaged_ms: from the first presentation toendorabandon;units: the distinct units presented;counted: the counted bytes of everyresponse;claim: theendobservation'sclaim.
§7.3 These are statements of the emitter, as the observations are (§1.1).
8. Privacy
§8.1 sid is fresh per session, and an emitter adds no persistent identifier. Telemetry
goes to the host that serves an object (Request mode) and to the content's own service
(Event mode), and, for a measure of telemetry-aggregation §3, to
its task's two aggregators as secret shares, each of which alone reveals nothing about the
measurement. This extension sets no consent requirement for the service's own mechanics:
using a distributor's WAI service is the person's agreement to how that service works. Where
a regime asks for a lawful basis for processing telemetry, the service records it in a
lawful-basis claim (jwp-receipts §2.4.6). What each key reveals, and to
whom, is §3.4's.
§8.2 Neither a URL's query nor a signed URL's token enters an observation. A reader
refuses an object.url that holds a ?, a #, a @ or a \: a query, a fragment,
user information, or a separator some URL parsers read as / and so a way to user
information past a check for it. It refuses rather than strips them, and a path that
holds a @ is refused with the rest. An emitter whose URLs carry a token in their
path MUST report url as null. cid's syntax (§2.1) admits a content hash or
a short identifier, and no syntax keeps an address, a token or personal data out of an
identifier: an emitter MUST NOT put an address, a query, a token or personal data
in cid, and reports the content hash where it has no identifier free of them.
§8.3 A Request-mode report travels with its request, to the host that serves the object and to no other party. Event-mode reports go to the content's own service (§8.1).
§8.4 A published aggregate covers at least 50 sessions per cell. A cell with fewer is suppressed, not rounded. Where the figures are aggregated under telemetry-aggregation, the service never holds a session's figures, and the floor is each aggregator's: a task's minimum batch size is at least 50, and an aggregator refuses to release a share of a smaller batch (§4.3, §5.2 there). This suppression still applies to what that extension does not aggregate (§9.1 there).
9. Conformance
§9.1 telemetry-conformance/ holds:
- observations of every event kind, with their expected CMCD reports and header shards
(
expected/cmcd.json); - observations every reader refuses (
invalid/); - CMSD-Static and CMSD-Dynamic descriptions with their expected fields
(
expected/cmsd.json); - session logs with their expected metrics (
expected/qoe.json).
§9.2 wai_telemetry verify requires every expected value byte for byte, and every
invalid/ observation refused.
§9.3 cml/differential.mjs builds each report from this extension's tables, encodes it
with an independent open-source CMCD and CMSD library at the versions
cml/package-lock.json locks, and requires the expected bytes; reads the
expected strings back as a dictionary, as a query string and as header shards, and
requires the report's members; and validates every report, and the Event-mode reports
together, with no error.
§9.4 The delivery-only reports of §4.3, with reports a reader refuses, and the key classes of §3.4, are checked in the corpus of telemetry-aggregation §12.