# WAI — changelog Notable changes to the WAI standard and the reference implementation. Newest at the top. WAI is a draft standard; until v1.0 freezes, the wire format may change and such changes are recorded here. ## v0.4.0 — current ### Staged measurement and the session claim in the browser - **The measurement core, bound for JS** ([staged-measurement](extensions/staged-measurement.md)). `wai-web` binds `wai::stage_measure` (`src/measure.rs`), the code `wai_stage_measure` runs, compiled to wasm32: `evaluateTitle` gives the text `eval --json` prints for a title (counted bytes, quality integers, first presentable bytes, the layering overhead's points, deduplication, the output tree), `simulate` and `simulateModel` the text `sim` prints, `countObject` one envelope's counted bytes or the code it is not countable with, `qualityRgb8` one picture pair's integers, and `outputRoot` the output tree's root. A title is read from a map of files; `titleMissing` names the next file it needs, so a page fetches them one at a time. Sums of squares are decimal strings, as the corpus writes them. - **The session claim, sealed in the browser** ([jwp-receipts](extensions/jwp-receipts.md) §2.4.12). The claim classes (`receipt_claims`) build for wasm32 with the measurement core, and CI builds them so. `sessionBegin` records a session at its entry object (`wai::session_recorder::SessionRecorder`): the decoder that decoded each envelope, each presentation with its output's digest, each object received. `finish` seals the claim with a key whose seed the page supplies, checked against the record first; a browser session's `energy` is `null`. `verifyClaim` checks a claim against the presentations and the objects as the claim-conformance runner does. - **Proved byte for byte.** `demo/verify_measure.sh`, run in the wasm job, requires every golden, refusal title, counted object, quality pair and simulator result of `staged-measure-conformance/` from the wasm32 build, and the native `eval --json` of every fixture to equal the wasm32 evaluation. A session recorded the way a page records one is the native tool's claim byte for byte and commits to the golden output tree; the wasm32, native and Python verifiers give the same verdict on it and on its altered variants. - **A recorder that holds the envelopes checks the claim against them.** A sink hands `SessionRecorder` each object's bytes (`received_envelope`): the recorder derives its content hash, counted and transferred bytes, and whether an entry names it, and `seal_checked` checks the claim against every envelope it holds, as a verifier holding the `envelopes` context does, so an entry naming a capability its envelope does not give never leaves the sink (`session_source_mismatch`). An object the sink does not hold is `received_unheld`, by its content hash; the recorder no longer takes an object's figures as told. The native tool's session over a title and the browser's `SessionHandle` both hold their envelopes; `decoderFor` refuses a capability that is neither the envelope's nor its fallback and requires the decoder's `first_unit`, `received` takes the bytes alone — bytes that are not an envelope included, recorded as an inert object neither counted nor presented, so the session accounts for every byte transferred — `verifyClaim` takes the envelopes, and integers from JS are refused unless whole and in range. - **An object is listed once, as its envelope gives it** (§2.4.12, the objects). No (content hash, path) is listed twice (`session_object_duplicate`): a sink that fetches an object again lists it once, its transferred bytes every byte of every fetch, as the recorder merges repeats. After the sums, an object is `presented` exactly when a decoder entry names it (`session_presented_mismatch`). With the envelopes, an object listed with a held envelope's content hash states that envelope's counted bytes, `null` exactly when it is not countable (`session_counted_mismatch`). - **Envelopes no entry names are received, not refused.** Inert bytes — of neither magic, or whose `security` member is not a security object — are refused as unreadable only where a decoder entry names them (`session_envelope_unreadable`); those, and an encrypted envelope, no entry names are a received object, uncounted and not presented, and one listed with counted bytes, even 0, is refused (`session_counted_mismatch`). A page that receives such bytes lists them and seals its session. - **The layers and units an envelope gives.** A decoder entry states `first_unit`, the unit at which its object's first unit is presented. With the outputs and the envelopes, each presentation through an entry whose envelope is held is at the layer count that envelope gives — 1 for a base or a rendition with no `refines`, `refines.layer + 1` for a layer (`session_layers_mismatch`) — and of a unit it covers: from `first_unit` plus the span's start, the span's count of units, or the base's units where a layer has no span or the object is a base (`session_unit_outside_object`). A re-decode decodes unit `unit − first_unit` of the entry's object. A base's units are read in its payload's own format — of its manifest capability's and its fallback's pinned formats, the one whose magic the payload starts with — whichever capability the entry names. `claim_verify.py` reads the units from the integer payloads as `wai-rs` does, and `differential.py` adds random sessions holding a base's and its layers' envelopes, as `WAI1` envelopes and as `WAI2` components. - **A layer is one a sink can apply, at its base's first unit** (§2.4.12). An entry naming a held layer a sink refuses is refused (`session_layer_not_applicable`): not a refinement or with a fallback, its `refines` malformed or absent, a `WAI2` refinement component that does not conform (SPEC §7.1), its base a layer, not admitted by the layer's capability or decoded only through its fallback, its span or runs outside a held base's units, or its payload not `WIR1` with a unit for each it covers (staged-delivery §4.1 steps 1–4 and 6). A `WAI2` component and its exploded envelope are one base in either form (staged-delivery §9). A held layer's entry states the `first_unit` of an entry it is applied over — naming its base through its own capability — and an entry naming a `stage` claim's base or a layer it lists states the unit `stage_sets` names that claim at (`session_first_unit_mismatch`). - **An entry names what a sink dispatches** (§2.4.12 rule 4): never a `WAI2` companion, a rendition or refinement whose payload does not match its `sha256`, a capability that is not dispatchable, or the fallback of a `WAI1` whose capability is companion-only (`session_source_mismatch`). - **One verdict for either name, by capability, and for a key holder** (§2.4.12). An entry naming a `WAI2` rendition by its exploded envelope's content hash, with only the `WAI2` held, is read as naming the rendition by rules 4–8; the exploded envelope's own bytes, held, are a separate delivery. A manifest or rendition is a layer only when its capability is refinement-only: a base carrying a stray `refines` is a base, as dispatch and selection already treat it (SPEC §4, staged-delivery §3), and `wai stage apply` now routes by capability. An encrypted envelope an entry names is accepted — a sink holding its key decrypts and dispatches it (SPEC §6.4 step 3) — and what the entry states of it is the signer's statement. - **A WAI2 offers its refinements only when every component conforms** (SPEC §7.1, *Selection*). The text now states the reference sink's rule: an envelope one of whose components breaks a rule of §7.1 or §5 (`check_component_roles`, now on `ManifestV2`) offers none of its refinements, and may still present the rendition it picked. `wai stage explode` follows it, refusing to explode a component of such an envelope that is of a refinement-only capability or marked a refinement; rule 5 matches it for the whole envelope; and http-delivery §7 step 4 states it for a client planning from a verified head: each refinement of a nonconforming envelope is dropped (`components_nonconforming`), never requested, one stating no `sha256` dropped for that too, on the range path and the whole-object path alike. A refinement is one whose `refines.base`, read on its own, is the pick's id (`container::refinements_naming`), so one whose `refines` is otherwise malformed is reported too; of a conforming envelope that is exactly the refinements whose `refines` is well formed. The HTTP corpus adds ONC's conforming twin, OCF, whose refinement is fetched and delivered on both paths, and ONM and ONX, a `refines` of the pick's id alone (reported) and of no entry's (not reported); `OR`'s refinement, which names the pick, is now reported. `falsify/` adds `conforming-withheld` and `refines-read-whole`, each with its `http_verify.py --mutate`. - **An encrypted envelope's clear parts are checked** (§2.4.12). SPEC §6.4 encrypts payloads only: rules 4–9 now read an encrypted envelope's manifest and rendition table as any held envelope's — the rendition an entry names, its capability, the component rules, a base's layer count, a companion-only capability, a fallback, and the classical length its table declares — and leave to the signer only what needs a payload: whether a payload matches its `sha256`, a base's units and a layer's `WIR1` payload. A `sha256` that is not 64 lowercase hex digits matches no payload, so an entry naming its rendition is refused, encrypted or not (rule 4, `session_source_mismatch`). - **The recorder states the classical length.** `SessionRecorder` derives `classical_declared` at the tally from the `WAI2` envelopes it holds, readable or encrypted, exactly as rule 9 counts them (`claim_receipt::session::classical_length`, which rule 9 calls too); `SessionRecorder::classical`, which a sink called by hand and the browser binding never could, is removed. A page that receives a `WAI2` it does not decode now seals its session. `verify_measure` seals two such sessions in wasm32 — over `counting/wai2-classical.wai`, whose second rendition is PNG, and its encrypted twin, both new in the counting set — checks the length against the rendition table, and the native and Python verifiers give the same verdicts. A limitation of this revision: the length comes from the `WAI2` envelopes held in full, so a sink that received a `WAI2` only as byte ranges, holding its head but not the envelope, cannot yet state that envelope's classical rendition (§2.4.12, `classical_declared` and rule 9). Recording such a session is HTTP session work still to come. - **A record or a derivation is no component in any role** (SPEC §7.1, *Component identity*; `record-or-derivation-as-component`). - **A record or a derivation is no companion** (SPEC §7.1, rule 1). A companion's capability is companion-only or unregistered; a capability registered in another kind — a payload, a record or a derivation — is refused (`standalone-as-companion`), which the reference now reads from the registry rather than from its list of media capabilities, which left records and derivations out. - **Every refusal accounted for.** The claim-conformance README's table lists each refusal a sink makes that decides whether a presentation can happen — staged-delivery §3, §4.1, §4.3–§4.5, §9; SPEC §4, §5, §7.1 — with the §2.4.12 rule and vector that refuse it, or the sentence of §2.4.12 that leaves it to the signer's statement. - **The browser claim is a release build.** `verify_measure_wasm.mjs` requires the wasm32 claim's `host.build.profile` to be `release` and its target `wasm32-unknown-unknown`. - **The build profile is the optimisation level.** `release` is a build at `opt-level` 2, 3, `s` or `z`, whatever its profile is named, and `debug` one at 0 or 1 ([jwp-receipts](extensions/jwp-receipts.md) §2.4.8's `build`): a WebAssembly build optimised for size is `release`. ### One Ed25519 rule for every signature - **SPEC §7 *Signatures*.** Every Ed25519 signature WAI defines is verified strictly: the key is its point's one canonical encoding and not of small order, `S` < L, `R` is not of small order, and the cofactorless equation holds with `R` compared byte for byte. The receipt extensions cite the rule instead of restating it; jwp-receipts §3.1 step 3 keeps its two reasons, `bad_signer_key` for steps 1 and 2 and `signature_verify_failed` for the rest. OER/2's chain signature keeps the rule oer-receipts §5 settles. - **One verifier in wai-rs.** `wai::sig::verify_strict` is the only Ed25519 check written in the crate but OER/2's. The video, SSAI, key-release, channel and package receipts (both versions), the provenance, ternary, spatial-audio, volumetric, film, score and haptics receipts, and a world's session receipt, ops and seal moved onto it; before, each read its key with the library's lenient decoder and checked the lenient equation, which accepts a key of small order (under the identity, `(R, S) = (B, 1)` verifies over every message), a second spelling of a key, and an `R` of small order. Claims, JWP groups and the key log, which held the rule in a copy of their own, call it too. It builds under the `ed25519` feature, the dependency alone. - **One verifier in Python.** The claim, JWP, signer, HTTP and packaging verifiers share `claim-conformance/_ed25519.py`, the rule written from RFC 8032 with the standard library. `pack_verify.py` had checked signatures leniently. - **Corpora.** Each receipt family's corpus carries the three signatures a verifier checking only the equation accepts — the identity as the key with `(R, S) = (B, 1)`, the identity spelled `y` = p + 1, and an `R` of small order under the base point — and every conforming verifier refuses them: new cases in jwp-conformance, signer-conformance and pack-conformance (`mutations/` and `receipts/`); claim- and http-conformance had them. No existing vector's expectation changed. ### The int_codec model and vectors: trained and coded on a generated signal - The int_codec model and vectors are now trained and coded on a generated signal; earlier revisions used a third-party recording. - `tools/wai_int_audio_export.py` reads no audio file. It writes its training signal and the clip it codes from a seed (16 kHz mono: tones, chirps, noise bursts and speech-like formant sweeps), in integer arithmetic, so a seed gives the same samples on every machine; `--signal-only` writes one with the Python standard library alone. Training is seeded from the same seed with deterministic algorithms and a fixed thread count: two runs gave byte-identical files. The seed and the command are in [`wai-rs/tests/vectors/int_audio/README.md`](wai-rs/tests/vectors/int_audio/README.md). - Regenerated through their generators: `wai-rs/tests/vectors/int_audio/` (the model, both bitstream forms, `audio.json`, `sha256.json`); `int-payload-conformance`'s `vectors.json` and its thirteen `wia1_*` refusal fixtures; the staged-delivery case `audio_int_codec_1l` and `index.json`; the staged-measurement audio title, `goldens.json` and `sim/results.json`. The `WIA1` format, the capability and the model's shape are unchanged. `wai_int_payload_vectors` now reads the place of a `WIA1`'s first table from the payload's header for the two fixtures that change it, so they keep their meaning on tables of another length (on the earlier vector the offsets are the same). - `tools/wai_audio_provenance_check.py`, run by CI: every committed audio vector is read to PCM and correlated, at every lag, with the reference recordings the CI host has; above 0.5 it fails, and a host with none skips it and says so. - Every corpus states its content's provenance: a `**Provenance.**` line in each `*-conformance/README.md` (OER/2's settled corpus aside) and a row per directory in `wai-rs/tests/vectors/README.md`, required by `tests/corpus_provenance.rs`. CONTRIBUTING states the rule. Two vectors are flagged there as not generated and to be replaced: `int_video`'s `video_real` and `neural-conformance`'s `photo`. ### Staged tracks over MoQ: a track per layer, a receipts track per track, delivery timeouts per hop, chains that recover - **Re-read against the drafts.** [moq-streaming-format](extensions/moq-streaming-format.md) names the revisions it was last read against, `draft-ietf-moq-transport-22`, `draft-ietf-moq-msf-01` and `draft-ietf-moq-cmsf-01`, and cites their sections. The re-read corrected: MSF's packaging values (`catalog` is a track name, not a value); what orders delivery (MOQT, subscriber priority first, then publisher priority, MOQT §5.1.2); how properties are carried (MOQT's track and object properties, of which MSF defines one); `SUBGROUP_DELIVERY_TIMEOUT`, which runs from the subgroup's FIN to its acknowledgement, not from an object's arrival; that MSF maps every object to a stream of its own, so a staged object never travels as a datagram; and that MOQT-22 replaced the Joining FETCH with fill fetch streams. - **§4.1 Staged tracks.** A stage set is carried one track per layer: base tracks and a track for each layer index, each object one whole envelope. Each entry carries `wai_staged` (`set`, `layer`, `lead_ms`). A set's extent is its tracks, layers 0 to the highest with a track at each, so an MSF delta update that adds, removes or clones a track changes it alone; `lead_ms` is read only while the set is live. Tracks are named by namespace and name (a namespace inherited as MSF says), unique in a catalog, one namespace to a set; the check needs the catalog track's namespace, and a checker that does not know it refuses a set that mixes declared and inherited namespaces; `renderGroup` is an integer that fits 64 unsigned bits and `depends` an array of strings, each naming a track of the catalog in the entry's own namespace; a layer track depends on a track of every lower layer and on none of its own or above. A sink checks a set's entries in a fixed order and reports the first failure (`staged-malformed`, `staged-set-incomplete`, `staged-depends`, `staged-grouping`, `staged-lead`, `staged-receipts`), treating a failing set's layer tracks as absent. On a live track each group carries one object in a subgroup of its own. The base track's publisher priority is first among the set's (lower numbers first), though a subscriber's own priority comes before it. A layer object's `OBJECT_DELIVERY_TIMEOUT` is at most its slack to the last unit it covers, settable per object; a base's never shorter than the time to its last unit. Timeouts run per hop and never decide lateness; a sink's longer target latency cannot extend a publisher's timeout. A subscriber caps its stage, and its receipts, by subscription. - **Receipts.** `wai_receipt_alias` (§4) is the `track_alias` a track's receipts sign: MOQT's Track Alias is chosen per session by each publisher on the path, so it is not that number past the first hop. Every alias in a catalog is an integer, compared as a number. `wai_receipts_track` names each media track's own **receipts track** (`wai-receipts`, at the media track's priority, no delivery timeout), whose group *g* carries the receipts of the media track's group *g*. No two tracks share an alias or a receipts track: a rendition of a layer, a clone among them, overrides both and has a chain of its own. Each receipt says which MOQT object it is about — `group_id` the MOQT Group ID, `object_id` the Object ID, the publisher's key and the track's alias — and a subscriber refuses one that does not (`moqt_mismatch`, a new jwp-receipts §3.3 reason) before verifying it, holding nothing of it. A subscriber sets no delivery timeout on its subscription to a receipts track. A MOQT relay that carries receipts as opaque payload is not a jwp-receipts §4 relay; the subscriber verifies every receipt (jwp-receipts §4.1 amended). - **Recovery.** A missing receipt is recovered by FETCH of its receipts-track group. A subscriber states its bound in counts: receipts held live for each chain (a time only beside it, never alone), held as history for each chain, and held across its chains. Past the live count the held receipts are refused (`parent_not_recovered`, a new jwp-receipts §3.3 reason) with their layers, the chain re-anchors at its next validly signed group, and the layers of the groups the anchor skips are refused. A subscriber that joins mid-stream declares a join and anchors at its first group. Each anchor leaves a gap, which a group linking to either end fills as history, in any order — the ascending order of a FETCH or a fill fetch stream included — and never as a fork; history waits under its own count. A group admitted before, offered again with the same bytes (MOQT can deliver one object twice, by a subscription and a fill), changes nothing (`accept again`), never `chain_break`, and its objects verify against it as against the head. jwp-receipts §3 step 3 defines holding, re-anchoring, joining and history, and says what an anchor does not prove; §6.1 says how anchors are reported and why the bounds hold across chains. - **Other text.** staged-delivery §8, §9 and §13; jwp-receipts §4.4 and §5.9; http-delivery §10.2 and Appendix C. The registry lists `wai_staged`, `wai_receipt_alias`, `wai_receipts_track` and `wai-receipts` as wire identifiers. - **Reference implementation.** `staged::moq` (feature `staged`): `StagedTrack`, `SetTrack::to_catalog_json`, `receipts_track_json`, `check_catalog` and `check_catalog_in`, `depends_for`, `live_object_rules_ok`, `priorities_ok`, `effective_timeout_ms`, `expired_at_hop`, `layer_timeout_ms`, `base_timeout_floor_ms`. `jwp_delivery::ReceiptChains` gains `with_hold`, `with_join`, `admit`, `expire`, `take_events` and `anchors`, and for MoQ `for_moq` (every count at most `MAX_HELD_GROUPS`) and `admit_on`; `HoldBound` states live, history and total counts (`from_json` refuses a statement without them); `ObjectReceipt::check_moqt`. Each group's signature is verified once, and a held group is offered again only when a group it links to is admitted (`verifications`, `reoffers`, `held_total` count them). Without recovery it behaves as before. `tests/staged_moq.rs` runs the corpus trace with signed receipts through `for_moq` under each subscriber's stated bound, per-track chains, dropped objects checked by inclusion and named by the relay's `delivery-gap` claims, and sealed `stage` claims. - **Corpora.** `staged-conformance/moq/`: the set's MSF catalog; catalogs around §4.1's check, among them type cases, namespace inheritance under the catalog track's namespace, another and none known, MSF delta updates (a clone of a layer refused unless it overrides its alias and receipts track) and conversion to on demand; and a trace of the set through one relay to four subscribers, two layer objects dropped on timeout and one forwarded within its timeout and late, one subscriber's receipt lost, recovered by re-anchoring and the gap then fetched and filled as history, and one subscriber joining mid-stream. `rate_port.json`: the `rate` predictor beside the f64 interpolation of the layered-PCM ladder idea, equal wherever that form is exact, differing (pinned) where it rounds next to a half. `jwp-conformance/` gains recovery vectors — among them a gap fetched in ascending order after a re-anchor, a joiner's ascending fill under a live bound of 4, the history bound passed, the bound across chains, and a group delivered twice — appended after every existing one. `staged_verify.py` and `jwp_verify.py` check all of it independently; `chain_gap_differential.py` lets both chain verifiers recover. - **joule-moq** (`joule-code/`): `staged` (the `wai_staged` member, the layer read from the envelope, the prototype `mrl_*` header compared and never obeyed), `ReceiptChains` per key and track, verifying each group's signature itself and knowing a group admitted before by its bytes, with the same recovery and bounds, and a `MockRelay` that forwards staged tracks on a caller-driven clock: per-hop timeouts, capping by subscription, one object to a live group, a receipts track per media track, `RelayStats` keyed by track, group and object, and send times past the clock refused. Its test `staged_tracks_drop_on_timeout_keep_receipts` runs the same trace. - No existing envelope, receipt or claim byte changes. Two existing jwp-conformance vectors change their expected outcome: an exact replay of a group the chain admitted (`chain_head_predecessor_replayed`, and g1 in `chain_legacy_replay_loop`) is now `accept again`, not `chain_break`. ### The session claim: what a sink reconstructed - **The `reconstruct-session` class** ([jwp-receipts](extensions/jwp-receipts.md) §2.4.12, the next number after `stage` and `delivery-gap`): a sink's statement of one session, about its entry object — the decoders it used, on which host and against which parameter sets; the path each unit took and why it switched; the presentations it made after their deadline; a commitment to every output it presented, by the root and leaf count of [staged-measurement](extensions/staged-measurement.md)'s output tree; the bytes it counted and received, and the objects it could not count; the classical rendition it did not need, by its `WAI2` table length; the `stage` claims it signed about the stage sets it observed; and, where metered, the decode figure of one completion with its acquisition class (`null` when unmetered, never 0). Its rules at §3.1 step 4, and step 6 against the presentations (the tree, first-use order of the decoder entries, paths, units, missed presentations, and re-decodes through decoders whose replicate class makes them byte-exact), the objects received (its entry object and every decoder entry's object among them), the envelopes received and the `stage` claims it names. Each rule and check has its reason in §3.1's table. Its capped lists are exact: the switches and the missed presentations are each listed in full up to the list's cap, and counted in full. - **It restates no `stage` claim.** A `stage` claim states one set's layer objects, its units at each stage on time and its delivered stage; the session names it by link hash with the title's unit that is the set's unit 0, and commits to every output across the title, late ones included. A sink signs one for each epoch of a set — before it forgets the set's base, and at the end — and names them all. Step 6 binds the two both ways: each set's units were presented; the session presented, at each stage, no fewer units than a `stage` claim counts; no unit at a stage beyond the layers that the claims whose sets hold it — every epoch's, and every base's over the unit — list as `applied` or `partial` (a `late` or `refused` layer applied to no unit); and each claim's base and arrived objects are among the objects the session received. Nothing binds a `stage` claim to a session beyond its base and key: one an earlier session of the title signed can be named. - **A decoder entry names the object it decoded**, and the envelope binds it. Its `object` is the content hash of the `WAI2` envelope whose rendition it decoded, or of the `WAI1` envelope; it is among the objects received (`session_source_not_received`). The envelopes a verifier holds — every `WAI2` envelope received, and any `WAI1` one — are among the objects received, read as plaintext envelopes (`session_envelope_unreadable`), and an entry whose object is one of them names what it gives: a rendition and that rendition's capability, or the `WAI1` manifest's capability or fallback (`session_source_mismatch`). A signer cannot relabel an entry whose envelope the verifier holds out of the re-decode check, by any route; which object an output came from remains its statement. An entry decodes one object, so a title's output-tree entries are (capability, pin, object) as well (staged-measurement Appendix C), and a session over a title still commits to the title's tree. - **The classical rendition.** An envelope's classical rendition is the first selectable one whose capability the codec glossary links from an entry of era `classical` and whose media is the envelope's; WAI's own formats, neural codecs, records, parameter sets and text never qualify. - **Authorities** (§7.5): a session needs `reconstruct-session`, not pinned-only. Its energy figure is a second item for confirmation, needing `reconstruct-session` and the energy authority of its class; a verifier reports the two side by side, so an origin can stand behind a session and not behind its figure. A session's subject's origin is not expected for it: a relying party names the origin it expects, if any. - **Figure profiles** ([energy-measurement](extensions/energy-measurement.md) §6.1): the `moq-jwp:claim-link` row widens to a session whose `energy` is not `null`, so a `measurement` claim publishes the bracket behind a session's figure. - **Delivery telemetry**'s `claim` member names a registered class now ([delivery-telemetry](extensions/delivery-telemetry.md) §2.1). - **staged-measurement §8.6–§8.7**: a tool probes a power reader's rail at rest before it brackets a decode — absent, stale or live by energy-measurement §3.1's rule — and reads an energy counter as power by its successive differences across its wrap, ending the samples at an interval that cannot be a reading — one implying more power than a processor rail draws, one longer than ten sampling intervals, or a difference of more than half the counter's range (a counter reset); a tool that signs a session claim seals a figure only where its verdict is clean, beside the `measurement` claim on it, its burst run on a power of two of workers, which the session's host states as its `threads`, so the claim names no more of the machine than the session (jwp-receipts §6.7). - **Reference implementation.** `claim_receipt::session` (the class), `session_recorder` (a sink's tally, each decoder named with the object it decodes; it verifies each `stage` claim as it records it — one for each epoch of a set — requires it signed by the key that finishes the session, and checks the sealed session against what it recorded and against those claims before it is returned), `key_log::Item::figure_of_claim`, `energy_report::rail_liveness`, `energy_burst::counter_power_samples` and `rapl_power_reader`, `energy_burst::{measure_on, power_of_two_workers}`, `stage_measure::run::completion`, `stage_measure::decode::{steps, prepare}` and `stage_measure::session`, and `wai_stage_measure bench`, which times each case of a title — one base's pinned decode or one layer's offer, its checks and hashing after the clock — over rotating rounds, compares every run's outputs with the evaluation's and aborts on a difference, records each run and the operator's statement of whether the machine was idle, meters the decode where a live rail allows, and with `--sign` seals the title's session and the report on its figure. A lib test replays every `staged-conformance/` case as a sink and requires the session it records, naming each epoch's `stage` claim, to seal. The output tree builds under `receipt_claims`; the switch reasons live in the ungated `delivery_terms`, which `telemetry` re-exports, so `receipt_claims` does not take `telemetry`. - **Corpora.** `claim-conformance/` gains the session vectors (§5.11) — every reason the class adds, output trees of 1 to 33 leaves, every context, the classical rule across the registry's kinds of capability, the caps at their edges, a base forgotten and decoded again (`staged-conformance/`'s `record_budget`, its envelopes and each epoch's `stage` claim) and every route by which an entry could be relabelled — and `signer-conformance/` the session's authority and its figure's; `claim_verify.py`, `signer_verify.py` and `differential.py` read the class. `claim_verify.py` reads envelopes with `staged-conformance`'s strict readers, and `differential.py` compares the two implementations across both stage bounds and many epochs, the caps, malformed and encrypted `WAI1` and `WAI2` envelopes, entries naming their objects or not, and every registered capability as a classical candidate. Every vector already in both corpora is unchanged, and the claim corpus pins the vectors before the class by a digest computed before it was added. `staged-measure-conformance/goldens.json` restates each title's decoder entries with the envelope each decoded, and so its output root. - **Wire.** No existing receipt, claim, pin or corpus vector changes bytes; a verifier of an earlier revision refuses the new class as `unknown_class`. A title's output root under staged-measurement Appendix C changes, its entries now naming their envelopes. ### Stage receipts: chains per key and track, objects verified by inclusion, `stage` and `delivery-gap` claims - **One receipt chain per publisher key and track** ([jwp-receipts](extensions/jwp-receipts.md) §2.4.1, §3 step 3, §6.1). `group_id` and `parent_receipt_hash` run per (`signer_pubkey`, `track_alias`). The text disagreed with itself: the signing payload's comment and §6.1's last bullet said per track, §2.4.1 and §6.1's first bullets per key; all now say per key and track. Receipts issued with `track_alias` `0`, or chained per track, verify as before. Receipts a publisher chained per key across non-zero tracks, as the earlier §2.4.1 allowed, now fail at §3 step 3: each group whose parent is on another track names nothing its own chain holds (`parent_not_held`). A stage set carries each layer on a track of its own, and a subscriber verifies the tracks it subscribes to without the receipts of the ones it does not. A group that passes §3 steps 1–3 can head its chain whether or not its objects arrived, so a dropped object does not break its track's next link. §6.1's checks are now real in the reference verifier (`jwp_delivery::ReceiptChains`): the monotonic `group_id` guard per key and track (`group_id_not_increasing`) and the freshness bound on `group_end` (`group_not_fresh`), off by default for labelled groups. - **A legacy link authenticates nothing** (§3 step 3, §6.1). The legacy payload signs neither `parent_receipt_hash` nor, through the link hash, anything that covers it, so a relay that rewrote one link — the first group's, to name the head — made the chain's whole history link again as new. The legacy payload does sign `group_id`, so a verifier now refuses a legacy group whose `group_id` does not exceed its chain head's, whatever its opt-in setting. A labelled replay fails its signature. - **Forks are told from gaps** (§3 step 3). With a chain's head held, a `null` parent or a parent that is another group the verifier holds is `chain_break`; a parent naming nothing it holds is `parent_not_held`. A chain's live edge is the highest `group_id` of a group that verified and arrived inside the freshness window, linked or not; a group whose signed `group_id` is below it is history, exempt from the bound, so a group a relay delayed past the window no longer wedges the chain behind it, and a gap of any length fills in any order. Both payloads sign `group_id`, so no rewritten link earns the exemption. A previous group of another key or track is not held: a `null` parent beside one starts the chain, any other parent is `parent_not_held`. `group_chain_break` in `jwp-conformance/` now holds a second chain head; its earlier input, a parent that is not the previous group held, is `group_parent_not_the_previous` and is `parent_not_held`. - **`inclusion-verified`** (§3, *Verified by inclusion*; §4.1, §4.4): an object receipt checked without the object's bytes — steps 1, 2, 3 and 5 — establishes that the publisher signed an object with that content hash at that position, and is never acceptance or delivery. A relay forwards the receipts of every object it does not forward, whatever the reason, and **MAY** say why in a `delivery-gap` claim (§4.2). `ObjectReceipt::verify_against(group, envelope: Option<&[u8]>)` returns `Evidence::{DeliveredVerified, InclusionVerified}`, which converts into staged delivery's `ReceiptEvidence`. - **The `stage` class** (§2.4.10): what a party observed of a stage set, about its base — the stage requested, committed, delivered and presented, as points of staged delivery's vocabulary `urn:wai:staged-delivery:1` (registered in [energy-binding](extensions/energy-binding.md) §7.2; §2.4.9's vocabulary row names it); units by stage; the tier and stage hash of what was presented; every layer object with its units, bytes, status, evidence and staged-delivery §4.2 reason; conflicts, deadline misses and envelope bytes. Six rules at §3.1 step 4, in order: the points; the stage hash; the units; the objects (in order and within the set, except an object refused before its span was checked; evidence `delivered-verified` for an applied layer wherever a delivered stage is stated; bytes for every object that arrived and none for a dropped one, fewer in all than the set's; no layer applied twice to a unit; a partial object over at least two units); the units against the objects, per unit (an applied object of layer *s* lies within the spans of layer *s* − 1, a partial one meets them, and the units at stage *s* or above are no more than layer *s* can have reached, a partial object counting at most its span less one); and the delivered stage, which must be the one the objects give. Delivered and presented are separate measures: a set whose layers were all refused meets its commitment with stage 0 presented. A delivered stage may be stated with no commitment beside it, unlike §2.4.9's rule 3: the observer states what arrived. Whether a commitment was met is derived, never stated. - **The `delivery-gap` class** (§2.4.11): a relay's statement that it did not forward the object at (`track_alias`, `group_id`, `object_id`) — `delivery-timeout`, `discretion` or `not-received` — checked at step 6 against the object receipt at those coordinates (`gap_subject_mismatch`). - **Authorities** (§7.5): a `stage` claim needs `stage`, a `delivery-gap` claim `delivery-gap`; neither is pinned-only, and `delivery` grants neither. - **staged-delivery §10** is split: §10.1 attestation of layers, which now names `delivered-verified` and `inclusion-verified` and the per-track chains, and §10.2 claims. `staged::claim::StageObserver` states a sink's `stage` claim from what it observed: the layers that arrived, with the layer and span their own `refines` states, those known only by their receipts, and every report the sink gave, the latest deciding. Every event carries the sink's epoch for the base (`StageSink::base_epoch`, which counts the times the sink has forgotten it): a later epoch starts the observation over, keeping only the envelopes the sink still holds for the base, and a claim never spans two epochs. Every case of `staged-conformance/` is replayed through a sink and its observers, and each claim seals and states what the sink did. [http-delivery](extensions/http-delivery.md) §10.5 says what an abandoned refinement is: `inclusion-verified`, and `dropped` or `late` in a `stage` claim; [staged-measurement](extensions/staged-measurement.md) §10.2 says the same of an object its simulator drops. - **Corpora.** `jwp-conformance/` gains the `chain` procedure — vector `v6_per_track_chain` (§5.9) and its neighbours: interleaved tracks, a track joined mid-chain, a link across tracks, forks, the guard and the freshness bound — and the `inclusion` procedure. `claim-conformance/` gains both classes (§5.10), every reason they add, and a context for a gap's object receipt; `signer-conformance/` gains the authorities they need. `jwp_verify.py`, `claim_verify.py` and `signer_verify.py` report every new outcome; `differential.py` generates `stage` bodies around every rule and gaps against receipts at and away from their coordinates, and `chain_differential.py` signs chains around §3 step 3 and §6.1 for both verifiers. Every vector already in the claim and key-discovery corpora is unchanged, and the claim corpus pins the ones before the new classes by a digest computed before they were added. In `jwp-conformance/`, `group_chain_break` alone changes, as the item on forks and gaps says. - No receipt or claim byte of existing material changes; one existing vector, `group_chain_break`, is given a genuine chain break, as above. ### HTTP delivery (Draft) - **The extension** [http-delivery](extensions/http-delivery.md) binds envelopes to HTTP, with no envelope byte and no manifest member changed: - the proposed media type `application/wai`, not registered; - `codecs` elements `wai1.`/`wai2.` that list the exhaustive union of the capabilities a variant may dispatch to or apply, with the floor every object of it offers carried separately (`X-WAI-FLOOR`, a DASH `SupplementalProperty`); - objects named by their receipt content hash (`.wai`), immutable, with no content coding and no `Vary`, and exactly one envelope each; - the `WHP1` head proof: the BLAKE3 chaining values that prove an envelope's head against its content hash without the rest of the object; - verified range fetch of one `WAI2` component that picks as whole-envelope selection does, and falls back to the whole object when a pick has no digest, a digest fails, or the plan needs most of the object; - receipt, key, grant, parameter-set and playlist sidecars; cache rules; priorities, deadlines and the carriage of enhancement companions, refinement components and layer objects ([staged-delivery](extensions/staged-delivery.md)); - HLS and DASH adjacency for on-demand, live and low-latency live; a capability probe that never asks a user agent about a WAI element. - **jwp-receipts**: §3 step 4 accepts the content hash a verified head proof establishes for an object fetched in part, and checks a receipt about a component's exploded envelope over the envelope the verifier builds. New §4.4 (objects not delivered) and §4.5 (an HTTP cache is not a relay). §1 and §7.9 point to the HTTP locations. moq-streaming-format §6 names the HTTP binding. - **SPEC**: §2 and §7.1 name the HTTP object and the `WAI2` head; the `wai.video.manifest` row admits `render` and `extras` on a playlist receipt. - **The registry's wire identifiers.** `codecs::registry::WIRE_IDENTIFIERS` lists the names the container, the two delivery bindings and key discovery put on the wire outside a manifest, each with its kind, defining section and status; `wai registry export` writes them to `registry/capabilities.json` (`wire_identifiers`) and the table in `REGISTRY.md`, and a library test checks each against the section it names. - **The reference binding** (`wai-rs`, feature `http`): `http_binding::{head, head_proof, plan, session, codecs, origin}` — the head and §5.1, the `WHP1` proof built and checked with BLAKE3's tree, the root checked before any covered byte is read as a head; plans by priority class; a sans-IO fetch session with §7.1's response checks, component digests, the whole-object fallback and refinement deadlines; the `codecs` grammar, union, floor and support; an origin's headers and Range answers. `container::select_entries` is SPEC §7.1's walk with the digest test passed in, which `WaiMulti::pick` and a client holding only the head both run, and `companion_indices` / `refinement_indices` bind by entry. The `wai` tool gains `wai http publish|codecs|origin|vectors|bench`. - **The corpus** [`http-conformance/`](http-conformance/): objects with their head proofs and ETag sidecars (a pinned learned image beside AVIF, JPEG XL and PNG of the one picture; AV1 at two rates with HDR10+ bound by id; a missing and a wrong digest; an AVC base with an LCEVC enhancement; a segment sequence; Appendix D's envelope; a head of several chunks) and cases for §4–§7. `wai_http_vectors verify` and the independent `http_verify.py` (standard library, written from the text, checked first against published BLAKE3 vectors) agree on every case. Falsification patches and Python mutations each must be caught, and a differential checks wai-rs's proofs of random envelopes in Python (`.github/workflows/wai-http.yml`). - **Cache conformance** [`cache-conformance/`](cache-conformance/): a driver for §9's rules, run against the reference origin (which must pass), against four negative-control proxies (which must each be caught) and against labelled caches where an environment provides them; reports name a cache by label, version and configuration digest only, and CI uploads them. - **Playlists and live** (http-delivery §8.6, §10.4, §11, Appendices G and H): - `hls_render::render_hls_with` renders on-demand, live and low-latency WAI media playlists (media and discontinuity sequences, a fixed target duration, parts with their server control, discontinuities, program date-times, `#WAI-LAYER` lines with `N` only, the open segment and its preload hint) and, under the legacy parameters with no extras, gives `render_hls`'s bytes; the module needs no dependency, and `wai_video_manifest` re-exports it under its old paths; - the playlist receipt (`ManifestReceipt`) carries `render` and `extras`; its signature binds a render digest only when a rendering is present, so every receipt sealed before verifies unchanged (one sealed by the earlier tree is pinned), and a rendering has one form; `seal_with`, the receipt file's strict JSON form and `mr/.json` are new, and a live playlist's revisions chain; - `http_binding::hls` reads media playlists, holds a WAI playlist adjacent to its classical one (durations to the millisecond, rounded half up, read exactly), renders the WAI media and multivariant playlists (bandwidths computed from the objects), and keeps the classical master to its one session-data line (`classical_changed`); `http_binding::dash` writes `wai.mpd` on the classical timeline; `http_binding::playlist_receipt` seals and checks playlists and live chains (`playlist_receipt_malformed`, `_mismatch`, `_invalid`, `_unchained`; `playlist_malformed`); - `wai http hls|mpd|seal-playlist|check-playlist`; the origin serves playlists to be revalidated on every use; - the corpus gains a staged pair with a layer over its second segment (O11), a low-latency set of two segments of twelve parts each, every part its own object (O12), a player fixture's WAI segments (O13) beside a playable classical presentation, the WAI playlists beside their classical ones, their receipts (the live revisions chained) and the published classical master. O10 was already the layer set, so these took the next numbers. - **Adaptation** (http-delivery Appendix F): `http_binding::abr`'s four reference policies (throughput, buffer, utility over buffer, look-ahead) in integers, the budget rule that never uses an unlabelled figure, an entry order the session applies on both paths (the selector's order now sees the verified head), and layer depth; seeded vectors per policy, the integer log, entry orders and depths, which `http_verify.py` reproduces. - **Playlists and adaptation, after review** (http-delivery §8.6, §11, Appendices F, H and I): - a live client walks back to the head it holds across revisions it skipped: every receipt is also served at `mr/.json`, the head fetched again is unchanged, and a walk is bounded (`playlist_receipt_parent_missing`, `playlist_receipt_walk_exceeded`); no revision lists another cid for an alias or a media sequence number an earlier one bound (`playlist_listing_rebound`); - Ed25519 is verified strictly (`wai::sig::verify_strict`: a canonical key of large order, `S` < L, `R` of large order compared byte for byte), by the playlist receipt and `http_verify.py`; - DASH adjacency is checked over an XML subset (Appendix I); `wai.mpd` takes the classical MPD's period, timescale, `t` and `d` and leaves a gap out; MPD values are escaped and checked as XML; - adaptation states have stated bounds (`abr_state_out_of_bounds`) and are computed exactly within them; under a budget only a labelled option within it is chosen; - a gap segment carries no parts or layer lines; the session-data line has one place in the classical master; a receipt file holds no member twice, no lone surrogate and no number past 2^53 − 1, a grant of no joule ceiling written `null`; `seal-playlist --grant`; the origin answers a far-future `_HLS_msn` with 400. - an MPD is held as written: timelines in runs, never expanding `@r`, compared run by run, at most 256 per document, and an element's attributes read in linear time (Appendix I); - a budget never raises the policy's choice: with nothing at or below it within the budget, option 0 and the budget `unmet` (replacing `floor`); - past the walk bound, a revision ahead of the head re-anchors the chain (`reanchored`), keeping its bindings, the revisions between unsigned; bindings key a line by its URL resolved against the playlist's (RFC 3986), a gap never withdraws a listed segment, and a binding is kept while one of the last 256 revisions lists it. - **What review found, fixed** (http-delivery §3.1, §4, §7, §7.1, §10, §13, Appendix D): - a layer object (a `WAI1` whose capability is refinement-only) is never dispatched: it is fetched whole at refinement urgency, verified against its cid and handed to staged delivery, and joins a union after `+` through `refines.base` (`not_a_layer`, `codecs_layer_unbound`, `layer_tag_malformed`). A `WAI1` whose capability is a payload is an ordinary object even when it carries `refines`: it is dispatched, described by its capability, and `not_a_layer` when listed as a layer. A layer is declared only in the `alt` of its own base's capability; - a refinement never moves its base: only base-class bytes count toward the 90 % rule, a refinement is always requested on its own, a refinement with no digest or a failed one is dropped alone on the range path and on the whole path alike, and a refused refinement response abandons only it; - a client presents a base as soon as it verifies and applies its refinements as they arrive; a refinement's deadline runs from that moment on the caller's clock (`FetchSession::base_ready`, `tick`), and `wai http bench` reports the time to the verified base apart from the time to the complete fetch; - after a component mismatch every byte past `covered_len` is discarded before the rest is fetched again; a base request never answered is `base_unanswered`; - `Content-Range` numbers are ASCII digits only; an element beginning `wai` in any case must be a well-formed WAI element; an empty element or a capability named twice is malformed; a `WAI1` naming a record beside a payload fallback is described by its fallback; a `codecs` value with non-ASCII text, and a head proof whose lengths reach the end of 64 bits, are refused rather than panicking; - the reference origin caps what it reads of a request head (16 KiB); gives the whole head a deadline from the connection's accept (default 10 s, `--read-timeout-ms`), answered by 408 however slowly its bytes trickle in; bounds each connection's whole life from its accept, writing included (default 60 s, `--max-connection-ms`), so a peer reading a large response slowly is cut off then; serves at most 64 connections at once (`--max-connections`), answering 503 to one more, with a cap per peer address only when `--max-per-peer` is given (it listens on 127.0.0.1, where every client has one address, and a default cap would let a few reconnecting clients shut the rest out); decodes `/__hits` as bytes; and refuses `\`, `:` and NUL in a path. The wire-identifier test reads each name in its section's body. - a verified base is decided: a component that verified is never read again, and of a refinement's response only the range requested is kept, so a refinement answered `200` with the whole object, a byte changed, never reaches the base. Every response carries the caller's clock (`FetchSession::on_response(…, now_ms)`), the base is stamped with the time of the response that verified it, and the deadline's arithmetic saturates. The corpus's `fetch_timed` cases run the session on a clock; `http_verify.py` agrees on them. The corpus gains a case for each, and `http_verify.py` agrees with wai-rs on them and on a seeded differential outside the corpus, in which a refusal must match by its code and each family reports how many cases it generated and how many were refused. - No envelope byte or receipt byte changes for existing material. ### Packaging: objects, the stage index, the floor, catalogs and the package receipt - **Objects** ([packaging](extensions/packaging.md) §5): each one envelope, named by its receipt content hash, with no manifest member naming a position, so a repeated piece is one object. The packer's base encode calls `choose_q` per piece. - **The stage index** `WSIX` (§6, `wai-rs` `stage_index`, feature `video_package`): per component, every object of the package by content hash, its lengths, role, layer, the tier and decoded-output digest of the prefix it completes, its SSE, and the class plan; its digest and a Merkle stage root; a named reason for every rule a verifier applies to an index and to the objects it lists. - **The floor** (§7): a supplied AV1 floor, checked against the keyframe plan (`wai-pack plan --out`) and the listing the policy pins, written as floor objects that carry `derived_from` and as CMAF files (`init.mp4` with `av01`/`av1C`, one fragment per piece). - **SPEC §7.1, *Derived renditions***: `derived_from` on a manifest or a rendition entry states that a payload presents another object and was made from it; an OPTIONAL member under the MINOR rule that never breaks a parse (§8). - **Catalogs** (§8): on http-delivery's Mode N, a classical master and floor playlist that name nothing of WAI; per class, its master (with the one SESSION-DATA line http-delivery allows), its WAI playlist with the codecs union and floor, and a WAI media playlist adjacent to the floor's, with `#EXT-X-GAP` for a piece the class takes as floor only and a `#WAI-LAYER` line for each refinement layer it lists (no package of this revision carries one: the packer refuses refinement rungs, §11.4); the classical and per-class DASH MPDs; the MoQ catalog; and a stage view per class. - **The version-2 package receipt** (§9, `wai-rs` `package`): binds the policy digest and each component's index by digest and stage root, with entry, object and byte counts every verifier recomputes; RFC 8785 JSON both ways; a measurement claim beside a metered figure. The version-1 receipt gains the same JSON form and keeps its bytes. Both share the figure profile `wai:video-package-receipt-id`. - **`wai-pack pack`, `verify` and `listing`**, and the corpus's golden package `video-base-01`, its index, object and receipt mutations with their reasons, and version-1 receipts as JSON. The registry lists packaging's wire identifiers. - No envelope byte or receipt byte changes for existing material. ### Packaging: a declared policy, its digest, and pieces cut at scene changes - **The extension** [packaging](extensions/packaging.md), first sections: a package is made under one TOML policy, `wai.pack.policy/1` (§3), read in a fixed order with every refusal named: value kinds a canonical JSON cannot carry exactly (floats, dates, negatives, integers past `2^53 − 1`), then the schema, every default written in, then the constraints across members (ladders and their refinement chains, classes, rules, deadlines against layer lags). The effective policy's digest is BLAKE3 over its RFC 8785 form, so two spellings of one policy have one digest. - **Pieces** (§4): `wai.pack.scenecut/1` cuts a video component at scene changes by integer histogram distance, with a bounded lookahead whose return and revisit clauses keep a short flash from cutting; a decision reads no frame past the lookahead, so live and on-demand packing cut alike. Pieces are bounded by `min` and `max`, aligned across components to the lead, and classified static, synthetic, high-motion or natural. Y4M and WAV sources convert by stated integer rules. - **`wai-pack`**, the reference packager, as far as a plan (`wai-pack policy`, `wai-pack plan`), with float arithmetic denied for the whole crate, and the corpus [`pack-conformance/`](pack-conformance/) with a second implementation in Python. - **What a packager may emit** (§3.6): only capabilities SPEC §5 registers as payloads with a pinned format, in a role each can play, and under `royalty_free_only` only royalty-free ones; every refusal names the capability (`capability-unknown`, `capability-not-payload`, `capability-candidate`, `capability-role`, `license-unclassified`, `license-refused`). - **One integer video encoder** (§5.5): `int_video_enc` codes RGB8 frames as the `WIV1` keyframe-0 payload SPEC §5 pins to `wai.video.int_motion`, with integer block motion search (whole- or half-pel) over the decoder's own prediction, residuals quantized in a closed loop, and one residual table for the clip; it states the SSE of its reconstruction. `int_payload::encode_wiv1_zero_motion` is its zero-motion case and writes the bytes it wrote before. The corpus holds its payloads for every piece of every fixture, decoded independently to the same frames. - **`wai::jcs`**: the canonical JSON form moves out of `claim_receipt` into a module of its own, compiled under every feature set, unchanged in behaviour. - **Choosing the step** (§5.6), specified and built ahead of the stage that writes objects: the largest of `q_candidates` (read as a set) whose SSE per sample, in thousandths, is at most `max_sse_px_milli`, else the smallest (`wai_pack::encode::choose_q`), with goldens recomputed independently. - **Reading what a packager is given**: a policy is read from its bytes (UTF-8, no byte-order mark, nesting at most 32 deep, `policy-toml` otherwise), ids are unique across every kind, a cut window holds at least 3 scores, a WAV block alignment is compared without wrapping, and no declared size sizes a buffer; the segmenter's state is bounded by its lookahead and window. The encoder codes in two passes and states what it holds (`int_video_enc::working_bytes`), and `rans::RansReverseEncoder` codes values last-first into the same bytes. ### Staged delivery in the browser, a base writer, a trace and a bench - **A base is bounded before it is decoded, and its chain from the start** ([staged-delivery](extensions/staged-delivery.md) §4.5, §4.2, §14). A base whose units' output, with 32 bytes for each stage its kept record holds, passes the chain budget is not added: its envelope is reported `chain-over-limit`, no chain is kept, its kept record is untouched, and its layers stay held. Where a base's capability declares a cost before decoding (the integer payloads SPEC §5 pins), a sink applies its cost limit to that declared cost first, and SPEC §4 treats a base past it as one it does not decode, moving on to the fallback; a capability that declares no cost (`wai.neural.int_synth`, `wai.neural.int_mlicpp`) decodes under its format's caps. The corpus gains `base_over_budget`; `staged_verify.py` implements the rule independently. - **The sink in the browser.** `wai-web` binds `wai::staged::StageSink` for JS (`src/stage.rs`). `StageBase.decode` decodes a base envelope by SPEC §4 through the integer path, with its pin verified against the parameter set the page holds in memory and under the page's cost limit, which is a required argument (`null` for the format's caps only; it bounds a base whose capability declares a cost); `StageBase.fromStage0` takes a base the page decoded elsewhere, which is **claimed**: its capability must be the manifest's or its fallback and one SPEC §4 would take (an inert envelope is refused), its tier is never above what the capability gives an unpinned base, and every report, unit and stage hash of its chain says `claimed`. A base's facts cannot be raised from JS (no `viaFallback` setter; `attested` only rises), and `addBase` takes the base. `StageSink` adds bases, offers layers, expires holds, removes, attests and forgets bases, and reads back every unit's stage, producer, frozen flag, tier, shape, output, stage hash (with its `claimed` mark) and squared error against a reference. `stageLayerInfo` reads a layer's `refines`, `WIR1` header, declared cost and byte split (framing, manifest, payload), and a base's declared decode cost, before either is offered. Reports and units come back in the corpus's own form. Media reach the page as `WAI1` bytes only. - **The browser equals the native sink.** `wai-web/demo/verify_staged_wasm.mjs` replays every case of `staged-conformance/` through the wasm32 build: each integer base is decoded in wasm32 and must give its `stage0.bin`, its tier and its fallback flag, and every report, final unit, stage hash and clip hash must equal what the native sink wrote. `wai-web-wasm.yml` runs it and watches the corpus; the crate's native tests replay the corpus through the binding's pure-Rust half as well. - **One base path.** `staged::decode_base` runs SPEC §4 for a base through `Wai::dispatch_verified`: its capability, then its fallback, each only where it is dispatchable, so a refinement-only or companion-only capability makes the envelope inert (`missing_capability`) and never reaches its fallback. It takes the sink's cost limit (checked before any symbol is decoded or output allocated, `cost_over_limit`) and its key log (SPEC §3.1 check 5 over a pin and over an unpinned set; none skips it). `decode_base_via` asks for one of the two capabilities and refuses any other (`not-offered`). The refusals come in a stated order: `not-wai1`, then `encrypted` (SPEC §6.4 before §4), then `not-offered`, then §4's; a parameter set is read only for a capability that reads one. Its own tokens are staged-delivery's kebab-case sink vocabulary; SPEC and payload codes pass through in snake_case. A pin counts toward the base's tier only where it resolved. A one-file set pinned without a form offers every held file to the pin, the member the capability reads first. The CLI and the browser share this path. - **A keyframe-0 base writer.** `int_payload::encode_wiv1_zero_motion` codes RGB8 frames as a `WIV1` keyframe-0 payload (zero motion, one block per frame, a closed group of pictures): the reference encoder the corpus generator used, now in the library. The corpus regenerates byte for byte through it. It refuses a clip past the format's caps before allocating (`too_large`). `wai stage base` writes a `wai.video.int_motion` base with it from raw frames, with its dimensions multiplied checked, and decodes it back before writing. - **A trace to check against.** `wai stage apply` is now a sink replay: envelopes are taken in order, routed by content (one with `refines`, or a refinement-only capability, is a layer; any other is a base, decoded by SPEC §4), and a base given after its layers releases the layers held for it. `--json` prints each step's reports in the corpus's form (bytes that are not a `WAI1` this sink reads, and a base it cannot decode, are the step's error by their code, and the replay goes on; a base read with `--stage0` is claimed, and its step, units, chain's reports and final entry say so), and every stage a unit reached in the step, in the order the sink applied the layers (`StageSink::set_trace`), so a batch that takes a unit up several stages lists each; every stage hash a delivery in that order reaches is in it. A test replays the corpus through it and requires the cases' reports and final units. - **`wai_stage_bench`.** Measures one staged set natively: per stage, the envelopes' bytes (framing, manifest and payload, and cumulative), PSNR or SNR per unit and over the clip from the exact integer squared error against the source at the units' final shape (a unit of another shape has no figure at that stage), the time each stage took (median and quartiles over at least 20 runs), the direct comparator (one layer per base at the stage's step, straight from stage 0, or why it does not apply) and every unit's stage hash. Energy is reported as unmetered. ### Staged delivery: refinement layers, base admission, and WAI2 refinement components - **The extension** [staged-delivery](extensions/staged-delivery.md): a stage set is a base and layers, each layer its own `WAI1` envelope naming, by content hash, the base and the producer each unit it covers is expected to have (`refines`: `base`, `prev` as one producer or runs, `layer`, `span`). A sink holds per unit a stage, a producer and a tier, and applies a layer per unit by one algorithm with a closed list of reasons (§4.1, §4.2): every envelope-scoped step (well-formed `refines`, a refinement capability with no fallback, the base present, not through a fallback, admitted, attested, a well-formed payload within the sink's cost limit and its chain budget) before the unit steps (conflict, readiness, geometry, deadline). Layers a unit is not ready for are held to a declared bound and released when its stage advances; layers released together form a batch, so two conflicting layers released together are both refused and the unit frozen, whatever their order. A layer that differs from one that already passed for the same unit freezes it at its stage. A unit's tier is the weakest of its base and layers, `entropy-consistency` does not pass through a layer, and a stage hash is stated only at `decode-equivalence`. Deferral, live deadlines (a sink never delays a unit for a layer, and counts the units presented below the stage their layers would have reached on time), attestation (a receipt verified without the layer's bytes never authorizes an application) and byte counting (envelope bytes; receipts, framing and parameter sets separately; never JSON or base64) are stated. - **Resource bounds** (§4.5, §14, A.5): a sink declares a chain budget per base, over its units' output, 32 bytes per stage, and the payload and declared working bytes of every layer it retains with one unit's output and 32 bytes for each unit it covers and has not been applied to; a layer that would pass it is refused (`chain-over-limit`) before any stream is decoded, and a layer no unit is held for is dropped. The declared working bytes count the stream index. A sink's work per layer is linear in its payload and declared symbols up to a logarithmic factor in its units, and §14 states how. A sink that drops a base's chain discards the layers held for it (`base-absent`) and keeps each unit's record that is not empty (its producers, a pending producer, its frozen flag), within a declared record budget whose oldest bases are forgotten first; the base decoded again advances only along that record, and a frozen unit replays only its own record, in order. Decoding a base again keeps its chain, and a receipt verified for the base, before or after it was decoded, makes it attested from then on, across dropped chains; a layer held for its base and offered again keeps the strongest receipt evidence, and over an attested base counts as arriving when its receipt was verified; a set split across bases shares one live clock (`first_unit`). - **The residual payload `WIR1`** (Appendix A): one rANS stream per unit; `clamp(pred + r·q)` with the identity, up2 (the half-pel arithmetic, odd sizes included) and rate (linear interpolation, `rdiv`'s halves away from zero, channels interleaved) predictors. It follows the integer payloads' rules: a parse in a stated order with the closed list of their codes, caps, a declared cost a sink reads before decoding, refusal at the first symbol a stream runs out on, canonical stream ends; and it reads no parameter set. - **Refinement capabilities** (SPEC §5, *Refinement capabilities*): `wai.image.int_refine`, `wai.video.int_refine`, `wai.audio.int_refine` and `wai.splat.int_refine`, each refinement-only, royalty-free, `StandardDefined` and exact given the base it refines. SPEC §8 adds the refinement-only role; SPEC §4 never dispatches one, and a sink that does not implement the extension finds it unsupported. The registry gains the `refinement` kind and role, and the export is regenerated. *Envelopes this makes non-conforming* (they still parse): a `WAI1` carrying a refinement capability without `refines`, with a fallback, or as a fallback; `refines` on any other capability; a refinement capability as a `WAI2` rendition or companion; a companion bound to one. - **Base admission is one table** (SPEC §5, *Base admission*; `codecs::BASE_ADMISSION`): the bases of each refinement and of the LCEVC enhancement layer, whose bases are unchanged. `enhancement_bases`, `companion_applies_to` and `admits` read it. - **WAI2 refinement components** (SPEC §7.1), on the component identity (`id`, `sha256`, `prior`; no other digest is defined): an entry with `role: "refinement"` carries `refines` naming entries by `id`, follows every selectable entry, is never selected, and is checked by `check_component_roles` in a stated order. *Exploding a component* writes its `WAI1` envelope, from `envelope_manifest` verbatim or from the `WAI2` manifest's members as written, with ids replaced by the content hashes of the named entries' exploded envelopes, so a component and the layer carried alone are the same object; `WaiMulti::pack` packs `WAI1` envelopes with their manifests verbatim, and they explode back to the same bytes; it refuses an envelope it could not give back (bytes around a manifest's JSON value or after a payload, or a manifest that inside a `WAI2` passes the depth limit) and checks its output before returning it. Each entry explodes on its own, so one that cannot leaves the others intact. A refinement's `envelope_manifest` names no fallback. A malformed `refines` or a mismatched `envelope_manifest` does not make a manifest unreadable. - **SPEC §2**: member names are unique within each object of a manifest, at any depth, and an integer member is written as an integer (`-0`, `1.0` and `1e0` are not); both readers refuse a manifest that breaks either. `span: null` is `span` absent. - **Reference implementation**: `staged::{StageChain, StageSink, wir1, predict, wir1_arith, tier, schedule}` (feature `staged`); `container::{Refines, Prev}`; `WaiMulti::{explode, explode_each, explode_all, pack, check_envelope_manifests, refinements_of}`; `container::check_unique_members`; `wai stage refine|apply|inspect|pack|explode` (`refine --out-dims` gives a layer's shape); `StageSink::{remove_base, forget_base, mark_attested}`; `wai wrap` refuses a refinement capability, and `wai inspect` shows `refines`. `RansEncoder` writes the bypass escape, and `int_entropy::hist_to_cdf` builds a table from a histogram in integer arithmetic. In `ternary::rans_encode_trits`, the encode call sat inside `debug_assert!`, so a release build coded no symbol; it now runs in every build. - **Conformance**: the corpus [staged-conformance](staged-conformance/) (image, video, audio and splat sets; cases for every reason of §4.2 and every `WIR1` refusal code, in the order the steps check them, and for §3's rules, holds and their bound, batches, frozen units, the chain budget, a base decoded again, receipt evidence and live deadlines, one clock across bases included; `WAI2` cases for each refinement rule of SPEC §7.1, for exploding entry by entry and for what a packer refuses; the live schedule), written and verified by `wai_staged_vectors` and verified independently by `staged_verify.py`, which shares no code with `wai-rs`; the pure- Python BLAKE3 moves to `claim-conformance/_blake3.py`, shared by both Python verifiers. Cross-architecture goldens pin every stage of an image, a video and an audio set; Kani proves, over bounded windows, the arithmetic step, the up2 bounds, `rdiv` and the rate indices. `rdiv` and the rate indices are computed in checked 64-bit arithmetic. ### Staged-delivery measurement: counted bytes, integer quality, the output tree - **The extension** [staged-measurement](extensions/staged-measurement.md): how a figure about staged delivery is measured and published. The counted bytes of an object are its envelope to the byte (`12 + man_len + P` for `WAI1`, `8 + man_len + 2 + 8n + Σ P` for `WAI2`), and an object is countable only where WAI checks the format of every payload it carries: the pinned integer formats and `WIR1`, by the parse a sink runs, for the capability and for a `WAI1` fallback with a pinned format. Any other capability, a classical codestream or an unregistered string among them, is not countable (`no-format-check`), and neither is a `WAI2` component whose `sha256` is not its payload's, nor an encrypted payload; no estimate stands in for a figure that is not countable. A figure carries the framing label `registered`. Quality is integers per presentation and per prefix, against the source and against the final prefix: SSE over RGB and over an integer BT.601 luma, a Q32 structural similarity over clipped 8×8 windows at stride 4 with an edge-aligned last window on each axis, so every pixel is in a window (bounded in 128-bit integers), PCM SSE and reference energy, and tensor SSE and largest difference, each pooled over units; decibels are display only. First presentable, layering overhead as two rate–quality points (never a ratio), delivery simulation, decode time and energy (only a clean attribution verdict is a figure; a rail that is not live is unmetered), deduplication, dated records, what CI asserts, and a media-class matrix are stated. The output tree (Appendix C) commits to every presentation, one SHA-256 leaf per (unit, layers), under RFC 6962's tree hash, by its root and leaf count together, with audit paths checked as RFC 9162 checks them under the committed count (`output_tree::OutputCommitment`). - **Measured titles** (Appendix D): a staged set over a staged-conformance base, read where it is, with only what the stage model does not carry beside it (unit duration, capture times, the source, single-layer references, the fixture tool's own reconstruction digests), and a content identity over the measured files. - **Reference implementation.** `wai-rs` feature `stage_measure` (pure integer, no ONNX, wasm32-clean; CI builds it for wasm32): `measure_framing`, `output_tree`, `stage_measure::{title, decode, run}`, which decodes each base through the sink's own base decode, `staged::decode_base_pinned` (which also gives the pin it verified), under the cost limit, and applies the layers through `staged::StageSink` under the chain budget, handing each stage's output over as it is reached. The tools' stated limits are `staged::TOOL_DEFAULT_LIMIT` and `staged::TOOL_DEFAULT_CHAIN_BUDGET` (staged-measurement Appendix D states each figure and why); `wai stage` (`apply`, `refine`, `base`), `wai_stage_measure eval` and `wai_stage_bench` decode every base under them, hold every layer and chain to them, and take `--limit OUT,SYMBOLS,WORKING,MACS`. A title's `limits` lower a limit and never raise one: each figure is the lower of the title's and the tool's. The corpus's `refusals/` hold titles a tool must refuse, with the code: a base past its chain budget, the two declared-clip bound vectors (refused before anything is allocated for them; a counting-allocator test shows it), the 1 GiB one again under a title that states limits of 2^40, two inert envelopes, and a layer past its title's cost limit. `tests/vectors/staged_bounds/` gains a base whose up2 layer declares 67,092,480 output bytes, which `wai stage apply` and `wai_stage_bench` refuse with no flag. Always on: `quality` and `content_dedup` (the package receipt's dedup figures now go through it; a map that gives one content hash two lengths no longer verifies); native only: `bench_env`, the machine record, which `wai_decode_bench` now prints from. The `wai_stage_measure` tool evaluates a title (`eval`), verifies the corpus (`verify`) and writes it (`gen-fixtures`, `generate-goldens`). - **The corpus** [`staged-measure-conformance/`](staged-measure-conformance/): a picture, a clip, an audio clip and a splat attribute tensor, each with its figures in `goldens.json`; `wai_stage_measure verify` and an independent Python verifier, `measure_verify.py`, both reproduce every figure, and CI runs both. - **The delivery simulator** (§10, Appendix B; `stage_measure::sim`, `wai_stage_measure sim`): a discrete-event model in integer microseconds of a sender putting a title's objects on one link (`strict-priority` or `fifo`; `datagram` or `subgroup` carriage with delivery timeouts that drop or reset; a layer cap; a VOD window or live capture) and a sink decoding and presenting them (workers, `base-first` or `fifo` decode, playout deadlines). Its figures are labelled simulated. Bases are sent, and decoded under `base-first`, in the order they became available. A refinement's stall of its base is then bounded by one chunk (strict priority) or a timeout and one chunk (`fifo`), plus one refinement decode (`base-first`): §10.4 proves it, for every plan with no base timeout, a bounded link schedule and `base-first` decode. It is also checked on generated plans drawn over that whole range and on the corpus's plans, and for each unbounded part a counter-plan's stall passes the bound of the same plan with that part restored. The corpus `sim/` and an independent `sim_verify.py` reproduce every result. - **Wire.** No envelope, payload, receipt, claim, pin or corpus file of an earlier revision changes bytes. ### Delivery telemetry: CMCD version 2 and CMSD for WAI objects - **The extension** [delivery-telemetry](extensions/delivery-telemetry.md): an unsigned, advisory observation about WAI delivery (no joule figure, no acquisition class, no quality figure; content and receipt hashes only as pointers), read strictly with exact members; its CMCD version 2 report, in Request mode for requests and Event mode otherwise (`e=rr` for responses, play states, custom events for switches, dispatch outcomes, layer-cap changes and dropped frames), with header shards; custom keys under `science.transaction.wai-`, each carrying a String; switch reasons; CMSD-Static and CMSD-Dynamic for a WAI object; session metrics from one session's log (video start time, exit before start, start failure, stalls, engagement, units, counted bytes); and privacy rules: a content id is a content hash or a short token, and an emitter puts no address, query, token or personal data in it; an object URL holding a `?`, `#`, `@` or `\` is refused rather than stripped, so no query, signed-URL token or user information enters an observation however a parser splits the URL; and a Request-mode report goes only to the host serving the object. Cohort assignment and capable share are not in this revision. - **Reference implementation.** `wai-rs` feature `telemetry` (no dependency; wasm32-clean): `telemetry::{sf, observation, cmcd, cmsd, qoe}`, with the RFC 9651 subset the reports use; the `wai_telemetry` tool. - **The corpus** [`telemetry-conformance/`](telemetry-conformance/): observations of every event kind with their reports, refused observations, CMSD descriptions and session logs. `wai_telemetry verify` requires every value; a differential encodes every report with an independent open-source CMCD and CMSD library at locked versions and requires the same bytes, reads them back, and validates them; CI runs both. - **Other text.** [moq-streaming-format](extensions/moq-streaming-format.md) §5 requires that telemetry **MUST NOT** restate the per-object property's figure, class or evidence; [energy-exposure-mapping](research/energy-exposure-mapping.md) gains a dated addendum. - **Wire.** No envelope, payload, receipt, claim or corpus file of an earlier revision changes bytes. ### The integer codecs' payloads pinned in place: `WIV1`, `WIH1`, `WIA1`, `WIS1` - **Pinned in place** (SPEC §5, *Payloads of the WAI-native integer codecs*, and the new extension [integer-payloads](extensions/integer-payloads.md)): the payload of `wai.video.int_motion` is `WIV1` with keyframe 0; of `wai.video.int_hyper`, `WIV1` with keyframe 1, a nested `WIH1`; of `wai.neural.int_hyper`, `WIH1`; of `wai.audio.int_codec`, `WIA1`, which carries the sample rate; of `wai.splat.int_codec`, `WIS1`, which carries each attribute's presentation range. These strings' rows named a decoder and no byte layout; three of them (`wai.video.int_motion`, `wai.audio.int_codec`, `wai.splat.int_codec`) also named the tool that wrote a JSON bitstream, which SPEC §5 called the bytes the codec normally emits. So the pin changes what these strings carry. SPEC §8 gains *Completing a registration*, which permits it for a WAI-native string whose row fixed no byte layout and lists the envelopes it makes non-conforming; from this revision their formats are fixed as SPEC §8 states. No new string is registered. `codecs::PINNED_INT_PAYLOADS` is the one table of strings and magics. - **The JSON is a converter input, never a payload.** The encode and export tools still write JSON; `int_payload::{wiv1,wih1,wia1,wis1}_from_json` convert it and state what it left implicit (an absent `mv_precision` is 1; the first frame carries no motion vectors; a short motion-vector list is padded with `(0, 0)`; the audio sample rate comes from the export's `audio.json`; a splat range must be exactly a binary32 value; the JSON `afrac` must be the model's; the channel count must match the medians, tables and offsets). *Envelopes this makes non-conforming:* a `WAI1` or `WAI2` that carries anything but the pinned format under these strings, a tool's JSON output among them. It still parses; a sink refuses it at decode (a JSON form with `bad_magic`) and does not try the fallback. Every emit path refuses to write it: `Wai::to_emit_bytes` and `WaiMulti::to_emit_bytes` (so the C packers `wai_envelope_pack` and `wai_envelope_pack_multi`, `WAI_ERR_INVALID_ARG`), `wai-web`'s `pack` and `packMulti`, and `wai wrap`. They run `container::check_int_payload_framing`, which parses the payload with the wire parser a sink runs and the capability's keyframe kind, so it checks every parse rule of the extension's §2–§6 and leaves to the decode only what needs a parameter set or the streams (§7); `wai inspect` reports what it finds. A refused emit is the new `EmitError::IntPayload`. An encrypted envelope's payload is ciphertext (SPEC §6), so the emit paths skip it and a sink checks the plaintext after decrypting (SPEC §6.4); `wai inspect` says so. No committed envelope or conformance corpus carried such a payload. The reference dispatchers (`Wai::dispatch_verified`, `WaiMulti::pick`, and so the neural sink, its C ABI and `wai-web`'s `dispatchCapability`) implement SPEC §6.4 step 2: they open no scheme, so an envelope whose manifest declares an encryption other than `"none"` is inert at them (`unsupported_encryption`; `container::Inert::Encrypted`, `container::Skip::Encrypted`, `neural::DecodeError::Encrypted`), and a payload sent in the clear under a claim of encryption is not decoded as plaintext. A `WAI2` manifest's `security` member (now stated in SPEC §7.1) must be a security object to be written (`EmitError::Security`, `container::manifest_v2_security`); one that is not is read as encrypted. - **One integer decode path.** `int_payload::decode_int` decodes the strings above and `wai.neural.int_synth` and `wai.neural.int_mlicpp` from parameter files held in memory, with no ONNX runtime and no filesystem, natively and for `wasm32`, through a dispatchable capability only. A refusal is one code from a closed list ([integer-payloads](extensions/integer-payloads.md) §8). `int_payload::resolve` verifies a pin over sets held in memory and runs SPEC §3.1's check 5 for a pinned decode and over the set an unpinned decode reads. The neural sink delegates every integer capability to it: integer video, PCM and splat attributes come back as `neural::Decoded::Int`, never converted; `ModelRegistry::builtin` advertises `wai.video.int_motion`, which reads no set; the C ABI refuses an `Int` output. A capability this build has no integer decoder for (`unsupported_capability`) moves dispatch on to the fallback, as SPEC §4 treats a capability a sink cannot decode. Check 5 covers every identity of the bytes a decode reads (SPEC §3.1): after a pin verifies, it runs again over the listing of the members read and a one-file set's file digest (`int_payload::read_identities`), so a set revoked under one identity is not reached through a pin naming it by another; and a listing beside an unpinned set counts only when it matches the files read. Outside the integer path, at a sink that holds a key log, an unpinned decode reads what it can read once — the registered file of a one-file set, or every file below the directory its decoder reads from (`pin::read_dir_set`) — and check 5 runs over every digest a pin over it could name: the file's, or the directory's derived listing (`pin::listing_of_dir`) and each file's, and each listing beside them that matches. The decode reads only those bytes, never the path again; where the key log holds the capability's sets to jwp-receipts §7.9, only the part §7.9 authorised (the whole, the files a granted listing names, or one granted file). So a revoked set is refused with no listing beside it, a granted listing authorises the files it names and nothing else, a file changed after the check is not read, and a file the decode needs outside the set is `set_incomplete` (SPEC §4, *Module authorisation*). `pin::PinAuthority` gains `requires_grant`, with a default. *Breaking:* `neural::Decoded` gains the variant `Int` and is now `#[non_exhaustive]`, so a `match` on it outside this crate needs a wildcard arm; `neural::ParamSource` gains `UnpinnedRead` (an unpinned integer set read into memory once) and `Checked` (what an unpinned decode at a sink with a key log reads, read once) and is `#[non_exhaustive]` too; `int_payload::IntParams` is opaque, built by `int_payload::resolve` (or `IntParams::none()`). - **Checked decoders.** `int_video`, `int_audio` and `int_splat` decode a typed stream that the binary and JSON forms both read into. Every size is capped at `2^26` elements (a `WIV1` clip at `2^30` bytes and `2^20` frames, its motion-vector field at `2^26` elements) the same on every target, every network's shapes are planned before anything is allocated, every latent and layer is checked for exactness, and a stream that runs out is refused at the first symbol it cannot give, so a build with overflow checks and one without accept the same bytes. A frame, a clip or a latent grows only as its symbols are decoded, to its declared size and no further, so a short stream stops early; `int_video` holds frames as RGB8. A stream that carries its symbols is decoded in full, and a few kilobytes can code a clip at the cap, so the header bounds the work: a sink reads the declared cost before decoding (`int_payload::declared_cost`, `container::declared_size`, the C ABI's `wai_int_payload_declared`, `wai-web`'s `intPayloadCost`) and applies its own size policy (integer-payloads §7, *Memory and work*). The cost counts the networks' multiply-accumulates layer by layer, exactly (`DeclaredCost::macs`, a `u128`; `macs` on `intPayloadCost`, a `BigInt`). The reference sink applies one: `neural::ModelRegistry::with_cost_limit` refuses a payload whose declared cost exceeds the limit before any symbol is decoded, with the new code `cost_over_limit` (`int_payload::check_cost`, `neural::DecodeError::OverCostLimit`), and dispatch moves on as for a capability the sink cannot decode (§8). `neural::declared_cost` reads the cost over the set a registry resolved, pinned or not. The C ABI's `wai_int_payload_declared` takes the set's path (or null for the header alone) and gives the working bytes and the multiply-accumulates too (`wai_int_declared_t` gains `working_bytes`, `macs_lo`, `macs_hi` and `cost_known`); its neural decode entry points no longer decode integer video, PCM or splat attributes, which they have no output kind for, and refuse them before decoding. A `WIA1`'s latent may be no longer than its `t_out` needs (`geometry_mismatch`). The JSON reader (`int_video::VideoJson`) caps every size before it pads a motion-vector list, and reads a field that does not fit the target's `usize` as absent rather than truncating it. In the browser, `decodeWiv1` keeps the decoder's frames (`DecodedVideo.frame(i)`) instead of copying the clip into one buffer. - **One refusal order.** The extension states the order of a decode's checks exactly (§7): the set's members, the set against the payload, every network planned with its caps, the shapes, then each stream symbol by symbol, a run-out before the value it would give. Networks are planned layer by layer in 64-bit arithmetic (`int_hyper_synth::plan`, the audio synthesis's plan), a size past 64 bits being past the cap, so a 32-bit and a 64-bit sink refuse a malformed network with the same code; a planned network's parameter lengths are checked before any symbol is decoded, and a convolution's padded side is capped. A set's entropy tables obey §2's length rule (3 to 4097 entries). The parser reads a table of one per channel table by table. A `WIV1` keyframe's planned size is checked against the clip's `h × w` before any of the keyframe's symbols is decoded, so `int_payload::declared_cost` and the decode refuse the same bytes with the same code; the Rust verifier checks that for every fixture. Fixtures pin each case. - **One header for a one-frame clip.** A `WIV1` of one frame codes no motion vector, so its `mv_precision` and `block` must be 1 (`field_range` otherwise); the converters write them so. The extension's §2 says a payload's hash identifies the payload, not its output (unused table entries, say, code the same clip in other bytes), and that one output is identified by its decoded digest (a clip's `clip_hash`). - **Canonical streams.** After its last symbol, each rANS stream of these payloads must have been read to its last word and end in the state `2^31`, where an encoder starts (`rans::RansDecoder::is_canonical_end`); otherwise the payload is refused with the new code `stream_noncanonical`. An unread word or another starting state coded the same symbols in other bytes. Every committed stream, the JSON forms' included, ends canonically. `int_hyper_synth` gains `decode_conditional_checked`; `int_synth` refuses a side of `2^64` or more instead of shifting by it. - **Parameter sets.** SPEC §5 names the members the integer decoders read: `g_s.whm`, `h_s.whm` and `entropy.json` for `wai.neural.int_hyper` and `wai.video.int_hyper`. `wai.audio.int_codec` (`model.wam`) and `wai.splat.int_codec` (`g_s.whm`) are registered as one file, which widens their registrations (SPEC §8). `wai registry export` writes nothing new: no exported field records a set's shape. A `WHM1` or `WAM1` network holds nothing after its last layer (`IntModel::from_bytes` and `AudioModel::from_bytes` refuse trailing bytes; every committed network has none), so one network has one file and one digest. - **Receipts.** A `wai.video.int_motion` receipt identifies its `WIV1` payload (`VideoReceipt::seal_decode_wiv1`, `verify_reconstruction_wiv1`), and `wai_video_meter` seals over it. The entry points that read the JSON form are deprecated; on a conforming JSON bitstream their output is unchanged, so every receipt sealed over a committed one, and every pinned digest of one, still verifies. They now read the JSON through `int_video::VideoJson` and the checked decoder, so they refuse JSON the old decoder took: a stream that runs out (it decoded on from the exhausted state) or does not end canonically, a table that breaks the table rules, `q` outside 1..=65535, an `mv_precision` other than 1 or 2, other than one motion-vector list per frame, vectors on the first frame or more vectors than blocks, a vector outside `i16`, a zero size, and a size past the caps. jwp-receipts §2.1's object content hash moves to its own module and feature (`jwp_hash`), which `receipt_claims` enables; `claim_receipt::object_content_hash` re-exports it, and no corpus changed. - **JSON readers that refuse.** `IntModel::try_from_json` and `CondBitstream::try_from_value` read the conditional codec's debug JSON without panicking; `wai-web`'s `decodeIntHyper` and `decodeIntHyperBin` read it through them, `CondEntropy::try_from_value` and `decode_conditional_checked`, so an empty model or bitstream, or sizes past the caps, are refused. `int_video::VideoJson` refuses a frame whose motion vectors are not a list. - **Browser.** `wai-web` decodes the four formats (`decodeWiv1`, `decodeWih1`, `decodeWia1`, `decodeWis1`); the entry points that read the JSON forms are for debugging and conversion. - **Conformance.** The payloads sit beside their JSON sources in `wai-rs/tests/vectors/`, written by `wai_int_payload_vectors`, with refusal fixtures and an index in `int-payload-conformance/`; `int_payload_verify.py` checks them independently in Python's standard library and decodes the `WIV1` keyframe-0 payloads in full; `byte-exact-conformance` parses and decodes the `WIV1` and `WIH1` payloads on every leg of its matrix. CI runs the new tests in both build profiles, the generator in the corpus loop, the verifier, a `cargo check` of each integer feature alone, and a `wasm32` check. ### The registry as one table, candidate capabilities, the codec glossary under test, and the public text retrofit - **Candidate capabilities** (SPEC §5): a registered string whose payload format no revision has pinned — `wai.tensor.nnc` and `wai.splat.sog` — MUST NOT be emitted, and a sink treats it as unsupported. This is a MINOR rule: it enforces the candidate registrations as they were made (§8). Envelopes it makes non-conforming: a `WAI1` naming a candidate as its `capability` or `fallback`, and a `WAI2` with a component of a candidate; they still parse. `codecs::is_candidate`, `Wai::check_candidates` and `WaiMulti::check_candidates` report them. `Wai::to_emit_bytes` and `WaiMulti::to_emit_bytes` check, then serialise: the C packers `wai_envelope_pack` and `wai_envelope_pack_multi` emit through them, the browser writers `pack` and `packMulti` refuse the same envelopes (`container::check_candidates_v1`, `check_component_candidates`), and `wai wrap` refuses a candidate before any other rule, in every place including a `WAI2` companion. A candidate's licence class is provisional: `wai.splat.sog` is classified patent-pool, conservatively (it was royalty-free, never established). `wai.splat.sog` no longer claims an MPEG attribution: no MPEG page or output document read names the format it was registered for ([research registry](research/media-codec-registry.md), *MPEG's Gaussian-splat and point-cloud work*). - **One registry table** (`wai-rs/src/codecs/registry.rs`, no feature): every registered string with its **kind** (`payload`, `companion`, `derivation`, `record`), its **status** (`registered` or `candidate`; `RegistrationStatus`), its **media** class, the SPEC §5 heading that registers it, the edition its registration names and the date it was registered; the named identifiers that are not capabilities (`wai.det.fixed64`, `wai.world.sim.*`); and the receipt labels no revision registers (`wai.quantum.{mitigate,decode,compile,rearrange,qbom}`). SPEC §5 states the kinds: records and derivations name no payload format. Every payload and companion capability carries a licence class (records do not). - **Dispatchable** (SPEC §4, normative): steps 3 and 5 take only a dispatchable capability, and a capability is dispatchable unless this revision registers it as a candidate, a companion-only capability, a record or a derivation. A string this revision does not register stays dispatchable — a later MINOR revision may register it (§8) — and is decoded only where the sink advertises it. `codecs::is_dispatchable` implements it and is the one predicate every dispatch and selection function of the reference implementation uses (`Wai::dispatch_verified`, formerly `dispatch_capability`; `WaiMulti::pick`; `WaiMulti::primary`; `RenditionMeta::is_selectable`; the CLI's `inspect`; and, below, the neural sink, the C ABI and wai-web); §7.1's selection sentence says the same. A later dispatch or selection function uses it too. - **`wai registry export`** writes `registry/capabilities.json`, its JSON Schema (`registry/capabilities.schema.json`), the generated table of [`REGISTRY.md`](REGISTRY.md) and **vocab v2** (`vocab/v2.{ttl,jsonld,shapes.ttl}`, namespace `https://wai.transaction.science/vocab/v2#`, one IRI per registered string, enumeration individuals named `-