# 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 ### `wai.neural.int_synth` through the integer operator set The first capability whose reference decoder decodes through `wai-ops/1`. - **The decode.** `int_synth::decode_bytes` checks the payload ([integer-payloads](extensions/integer-payloads.md) §13), lowers the set by `int_synth/1` ([integer-operators](extensions/integer-operators.md) §13.1), loads the graph under `acc64` (load steps 1–8, the last being the bounds certificate) and runs it through the operator set's interpreter, with the fast transposed-convolution kernel behind it (`wai-rs/src/int_ops.rs`). Every committed output is unchanged, byte for byte, on every platform the byte-equality matrix covers and in the browser crate, and every committed refusal keeps its code (`payload_malformed`). The direct decode it replaced is kept, test-only (feature `legacy_int`): `tests/int_ops_differential.rs` decodes random sets and payloads both ways and requires the same output or both refusals outside the planned breaks, and `wai-ops-differential.yml` runs it for an hour in each build profile every night. - **Planned breaks** (SPEC §8, integer-operators §15.3): new refusals of input no committed corpus contains. `wai.neural.int_synth` refuses a payload whose reserved byte is not 0, whose `C` is 0 or whose latents exceed `2^26` values; a set with no layers, with a layer whose `out_ch`, `k` or `stride` is 0, with a field of `2^32` or more, or with a shift of 128 or more; a set or payload a cap refuses (a tensor, or a transposed convolution's uncropped output, past `2^26` elements); and a set whose certificate does not meet `acc64`. A shift from 64 to 127, which a build used to mask or panic on, now has its exact meaning. A scan of every committed `int_synth` set and payload finds none affected. The text now states the two rules the lowering already made and the payload text did not: at least one layer, and `out_ch ≥ 1`. - **Its `ops` member in the registry** ([integer-operators](extensions/integer-operators.md) §11). `registry/capabilities.json` gives `wai.neural.int_synth` the member `{"set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "int_synth/1"}`, and the schema describes the member. SPEC §5's row cites the payload and set ([integer-payloads](extensions/integer-payloads.md) §13) and the lowering (integer-operators §13.1), and SPEC §7 says that a `NeuralIntegerExact` capability that decodes through the operator set names it in its `ops` member. ### Vector graphics (Draft): the sink as a WebAssembly 1.0 module, in three browser engines Additive. No payload, outline set, vector or digest changes. - **The sink's exports.** `wai-vector/wasm` exports the sink of [graphics-vector](extensions/graphics-vector.md) behind a C ABI, with no bindings generator and no imports. `wv_add_set` holds an outline set a payload may name; `wv_open` loads a payload to a handle, or to its refusal's code; `wv_render` gives a frame's `RGBA8P` at a listed size and rate, or the request error's code; `wv_cost` gives a listed size's cost; `wv_transcript` gives the transcript, and `wv_projection` the projection at a frame of a listed rate, each as the JSON of §5.4, or the request error's code; `wv_sha256` gives a digest; `wv_close` releases a handle. A result is read at `wv_out_ptr`, `wv_out_len` bytes of it. No export takes or returns a 64-bit integer: a frame number goes in as two halves. - **The package.** `wai-vector/wasm/build-pkg.sh` builds the module a site serves: the sink's exports alone, with no glue script. It is WebAssembly 1.0 and nothing later: no SIMD, no threads, no bulk memory, no sign-extension operators, no reference types. The target is `wasm32v1-none`, whose precompiled `core` and `alloc` are 1.0 themselves, so the crate is now `no_std` and carries its own allocator. The build then reads the module back: `wasm/check-mvp.mjs` decodes every section, type, segment and instruction, and refuses anything later than 1.0, any import, any 64-bit integer at an exported function, any floating point, and any exported function the script does not list. - **Three browser engines.** `wai-vector/browser/run.mjs` serves a page, the package and the corpus, and in each engine loads every payload of `vector-conformance` to its expected code, checks its SHA-256 and each listed size's cost, and renders its frames through the page's hook, `window.__wai_overlay.frame(size, rate, g)`, which returns the renderer's own buffer: every listed frame at sizes up to 1280 × 720, and sampled frames above. The page hashes each frame with the module's SHA-256 and with the platform's digest. The sampled frames are also sent to Node as bytes, and Node hashes them itself. Where a payload's expected file states a text record, the page's hook also gives the sink's transcript and its projection at every frame of every listed rate, `transcript()` and `projection(rate, g)`; written out as the record's lines, they must be the committed `.a11y.jsonl` byte for byte. The recomputation module then recomputes every expected file and authoring form in each engine. Nothing is read from a canvas. A run passes only when all three engines ran: a missing engine is a failure. - **CI.** `wai-vector.yml` gains the `browser` job: the package built and read back, then the three engines, in a container image pinned by digest. ### Vector graphics (Draft): text and accessibility, §5 of `wai.graphics.vector` - **[graphics-vector](extensions/graphics-vector.md) §5 is written.** A payload carries its words beside its glyphs, and this revision says what a sink computes from them and what it owes the viewer: - **The projection and the transcript, byte for byte (§5.4).** A block's active set is the union of the effective windows of the runs and paths that draw it, cut to the document's window, sorted, with windows that overlap or touch merged. The projection at a frame is every block active at the frame's tick, in reading order; the transcript is every block with its active set. Both are JSON with a fixed member order and exactly three string escapes. A block's `purpose` is written as the byte carried, an unregistered code included; `mode` is in every node; a ruby link is written as the base block's index with the range's start and length; `decorative` blocks are in neither. Opacity, position and clips do not enter either value. A payload's *text record* is its transcript and every frame's projection at every listed rate, one value a line: text-equivalence is byte equality of it. - **Encoder rules (§5.1, §5.3)** that a verifier checks and a sink does not: one base direction for a block, one joining sequence in one run, vertical text and its `mode`, Normalization Form C, every byte of a string in exactly one cluster, no zero-glyph cluster for a visible character, and no colour glyphs. - **The purpose vocabulary (§5.2),** with how each purpose is exposed and whether it is announced by default. - **Exposure (§5.5).** A presenting sink exposes the current projection to the platform's accessibility interface, updates it when the projection changes, inserts strings as text and never as markup, and keeps within stated announcement limits. What assistive technology then says is not claimed. - **Whether the words match the glyphs (§5.6):** the chain from a parent font to an outline set, from a text block to glyph IDs, and from glyphs to the overlay layer; what an authoring receipt records; and what the chain does not reach. - **Captions under the viewer's control (§5.7).** Captions travel as timed text. A sink that cannot read the platform's caption preferences does not draw `caption` or `subtitle` items, and a frame it draws without them is not the published frame. - **`wai-vector`: the text layer.** `wai_vector::text` computes `text_hash`, block identities, effective windows, active sets, the projection, the transcript and the text record, in a sink's build, and a verifier's check of the cluster rule. The tool gains `wai-vector a11y` (the text record, the transcript, or one frame's projection) and `wai-vector text` (the text digests and each block as a reviewer reads it). The corpus-seeded fuzz holds the text layer of every payload it accepts: the transcript is JSON that gives back each tag and string, and a projection lists exactly the blocks the frame procedure draws at that tick. - **`vector-conformance`: the `a11y` group.** Each payload's expected file states the SHA-256 of its text record, and the record is committed beside it, so every fabric leg recomputes it: the worked example of §5.4, windows and a fade to nothing, a `decorative` block, text alternatives of paths, reading groups, ruby over part of a base, registered and unregistered purposes, merged and clipped intervals, windows cut by enclosing groups, the three writing modes, four rates over one document, strings that take each escape and strings that take none, and a payload with no text. The `canonical` and `refusals` groups gain text-block cases: an ungrouped block between groups, ruby links to a later block and ranges that end inside a character or at the string's end, and pairs that pin where `TEXTS` sits in the record pass and that the text-block rules come before any outline set is read. ### Reader limits that bound memory: manifest values, and text decode Two reader limits join the manifest length limit (SPEC §2, *Reader limits*), and with them a reader's memory is bounded by what the specification states, not by what an envelope asks for. `SECURITY.md` listed both as known gaps; it now states the bounds and the tests that measure them. - **Manifest values.** A reader need accept no manifest of more than 2²² JSON values, the manifest object and every value in it at any depth. The reference readers count the values before they build anything, refuse one past the limit (`manifest_too_many_values`), and build what they read at the size counted: at most 128 bytes a value and one a byte on a 64-bit target, where a manifest of small values at the length limit took several gigabytes to parse. The stdlib Python verifiers refuse the same manifests, and the HTTP corpus holds a case at the limit and one past it. - **Text decode.** A `wai.text.zstd` or `wai.text.xz` payload decodes to at most 2²⁷ bytes and declares at most 2²⁷ bytes of window or dictionary; a reader may refuse more (`text_decoded_too_long`, `text_window_too_large`). The reference decoders write into a buffer never sized past the limit, refuse a Zstandard frame that declares a longer content size before decoding anything, and give the decoding library the window limit, so a declaration past it is refused from the stream's header. The reference encoders refuse input past the length limit. The C entry points `wai_text_zstd_decode` and `wai_text_xz_decode` are bounded the same way; the header gains `TEXT_DECODED_MAX_LEN` and `TEXT_WINDOW_MAX_LEN`. - **Planned break for writers.** A conforming encoder writes no manifest past the value limit and no text payload past either text limit. No committed envelope or vector is past any of them, and no digest changes. - **The codec wrappers in CI.** `wai-conformance.yml` gains `codec-wrappers`, on the macOS runner: the wrappers' fuzz smoke, the text decode limits and the C entry points, built with `codecs`. The step fails if any of its test binaries ran no test, which is what a build without the feature does. ### Integer operators: the reference crate, the bounds pass and its certificates The crate, the tool and the corpus that [integer-operators](extensions/integer-operators.md) (Draft) names exist. No capability decodes through them yet: each capability's reference decoder is still the one its payload extension names. - **`wai-ops`**, the reference crate, `no_std` with `alloc` and no dependencies: the 512-bit saturating integer of §8.1, the canonical JSON writer of §8.5, the `WHG1` parser and writer, the load-time and run-time plans, every graph operator of §5, the stage operators E1–E13, and the lowerings of §13. - **Load step 8.** `wai_ops::bounds` runs the bounds pass of §8.1–§8.5 and writes the canonical certificate. The interpreter runs only a loaded graph, so nothing decodes a parameter set its certificate does not admit. `interp::run_observed` reports every value a run forms; `bounds::Within` holds a run to its certificate, and every golden decode runs that way. - **`wai-verify`**: `bounds`, `lower` and `vectors`, with `--check`, `--witness` and `--advise`. - **`wai/ops-conformance/`**: per-operator, stage, graph, load and lowering vectors, the lowered graph of every committed parameter set, and each set's certificate. - **Two certifiers.** `tools/wai_ops_check.py`, written from the extension text and sharing no code with the crate, recomputes every vector and every certificate byte for byte. - **`wai_ops::limb32`**, a reference model of the 32-bit arithmetic the `acc32` profile's two products need, checked against 64-bit arithmetic. - **Model checking.** The operator set's harnesses are in `wai-ops/src/proofs/`, hosted by `wai-verify` and run by `wai-formal-verification.yml`. A harness that does not yet verify within the job's budget is kept, behind `cfg(kani_open)`, and is not run or counted as proved. - **`wai-ops.yml`** runs the crate's tests and `vectors verify` natively, under wasm32-wasip2 and under wasm32 in Node, and runs the checker. ### wai-quantum 0.4.3: metered runs, placement by run, and the fabric wrapper Additive. The default features, `full`, both implementations of the WebGPU member, and every existing pin and digest are unchanged; runs, receipts and members stay unmetered unless a caller supplies a meter. - **Run meters.** `quantum_fabric::RunMeter`: `start` just before a run's first submission, `stop` after its last readback, returning the microjoules in the window and their `EnergyClass`, or none when the window cannot be attributed to the run alone. The trait is member-neutral, and no implementation ships in the crate. A reading reaches a result only when `check_label` accepts it; otherwise the run is unmetered (zero, no class). Every start is matched by one stop, a refused run's reading is discarded, and a meter that cannot start is never stopped. - **The WebGPU member, metered.** `WebGpuMember::simulate_metered(c, start, meter)` returns the state with the accepted reading. Its window runs from the first submission to the last readback: the device's work, and the host's waits and copies between them; planning, buffer creation, the upload's staging and the conversion to a state vector are outside it, and the member's `detail` says so. A circuit refused before any submission opens no window. `member_metered(corpus, meter)` certifies as `member` does and gives each entry the figure its runs earn; `blocking::{simulate_metered, member_metered}`. - **Placement figures from metered calibrations only.** `EnergyCalibration` (`new`, `add`, `runs`, `finish`) turns metered runs into a `KernelEntry::energy` figure: the total energy over the operations performed (`pair_updates`: one pair of amplitudes under one gate), in nanojoules rounded up, with the runs' class, whose uncertainty is the largest of the runs' plus what the rounding adds, over their windows together. An unmetered run, a refused label, a second class, or an uncertainty that no longer fits its field once the rounding is added leaves no figure. `detect_local_metered(corpus, meter)` gives the CPU reference its figures. Because `place` ranks a known figure before none, figures go on the CPU reference and on the members placement compares it with, or on none. `place` is unchanged; `place_for` and `Fabric` hold the rule themselves, and read figures on only some candidates as figures on none. - **Placement for a run.** `place_for(members, kernel, required, predict_pj)` places by the least predicted picojoules when every candidate has a prediction, ties falling as in `place`. Otherwise predictions are not used, and the order is `place`'s, except that figures on only some candidates count as figures on none. Under synthetic models with a fixed cost per GPU run, a 3-qubit circuit stays on the CPU and a 24-qubit one goes to the GPU. - **The fabric wrapper.** `Fabric` holds members with their `Runner`s (`CpuReference`, and `WebGpuMember` for its own implementation) and optional meters (`add`, `add_metered`, `set_model`, `order`, `run`). `run` places the circuit, falls back down the placement order when a member refuses with `MemberError::{TooWide, OutOfRange, Memory, Timeout, Device}` (`falls_back`), stops at an invalid circuit or a defect, and seals the `FabricReceipt` with the member and implementation that produced the result and its accepted reading. The refusals (`Refused`) are listed beside the receipt in `FabricRun`, or in `FabricError`; the receipt's signed bytes are unchanged. `WebGpuError` converts into `MemberError`. A runner states the member it is (`Runner::member_id`), and a run that reaches a member paired with a runner of another id is refused there as a defect, so a receipt cannot name a member that did not produce the result. `ready(future)` gives the output of a future that is ready on its first poll, which is how a native caller drives `run` without an executor; `&R` is a `Runner` and `&mut M` a `RunMeter`. - **Tests.** CPU-side tests, native and under wasm32-wasip2 (each required by name in the `quantum-wasip2` job), cover the readings, the calibration, figures on both members or neither, `place_for`, the fallback, and metered fabric receipts. The device test `g13_metered_runs_and_the_fabric` runs the member under a test meter, calibrates it on random-v1 with the pinned digests, and runs the fabric with the member held to three qubits: a wider circuit falls back to the CPU reference, a narrower one is produced on the member and metered. It runs the tile implementation the same way, whose receipt names it, and requires a member paired with another member's runner, or with the other implementation's, to be refused. The member script requires it, and a fabric line per declared adapter. ### wai-quantum 0.4.2: tile runs, and the WebGPU member in a browser Additive. The default features, `full`, the per-gate implementation and every existing pin and digest are unchanged. - **Tile runs: a second implementation, `wgsl-lanes32-tile/54e443520c020013`.** `plan::Config` (`PER_GATE`, `TILED`; `tile_size`, `run_cap`), `plan::Step::Tile`, `compile_with`, `schedule`, `execute_from`, `PlannedGate::{new, is_identity, is_well_formed}` and `TILE_BITS_MAX`; `PLAN_REVISION` is 2. A tiled plan applies each maximal run of consecutive gates that act inside a tile (diagonal gates on any target, others with their target inside the tile) to tiles of up to 1024 amplitudes, in op-log order, and leaves out exact identities only (circuit/1's `P(19..=32)` is `2^30 − 1`, not the identity, and stays). The CPU executor mirrors a tile kernel's index arithmetic and equals the per-gate program on any words; every configuration certifies with the lanes32 pins. A program whose gate's `diag` disagrees with its words, or whose op index is `u32::MAX`, is refused by the executor and by the member. `wgsl::TILE_SHADER` (`lanes32.wgsl` followed by `lanes32_tile.wgsl`, entry point `apply_tile`: one workgroup of 256 per tile, the tile updated in place in the state planes, a storage barrier between gates, the tile size in the run record, never in the text; a run record whose tiles or grid do not cover the state exactly once is refused) has its own id (`tile_implementation_id`), records (`tile_gate_record`, `tile_run_record`), grid (`tile_grid`) and mutants (`TILE_MUTANTS`, with the detectors `Race` and `Malformed`, `race_witness`, `malformed_tile_run`). `WebGpuMember::tiled()` gives the same device running it, probed and certified on its own; the member splits a run into dispatches of at most `SUBMIT_BUDGET` pair-updates. Discovery opens per-gate members, which are the fallback. Given both implementations' entries for one member and no energy figures, `place` breaks the tie by implementation name and places the tile implementation (`wgsl-lanes32-tile/…` sorts first). The checker holds `TILE_SHADER` to its own rules, barrier placement and barrier uniformity included, and its SPIR-V to the validator; the member's device tests, the member script and the browser leg require the four pinned digests under both implementations and every tile mutant caught. `tests/fabric_expected.rs` (not in the package) holds `quantum-conformance/fabric/expected.json` to the crate. - **The member in a browser.** wai-web's feature `quantum_webgpu` binds the member for JS (`quantum_webgpu_discover`, `QuantumWebGpu`: `certify`, `statevector_hash_hex`) in a bundle of its own (`--out-name wai_quantum_webgpu`); the default build and its bundle are unchanged. `demo/quantum_webgpu.html` runs the member in a worker, its kernel compiled by the browser's own WGSL compiler. `quantum-conformance/browser_webgpu.mjs` drives the page headless and requires the four certificates bit-exact with the pinned digests (`quantum-conformance/fabric/expected.json`), every vector's statevector hash, every reject file refused with its class, and a circuit wider than the member holds refused. The `checks` job builds the bundle, and a `browser` job runs the page on macOS. ### wai-quantum 0.4.1: the fabric's WebGPU member Additive. The default features, `full`, and every existing pin and digest are unchanged. - **Certification one case at a time.** `quantum_fabric::Certification` (`new`, `add`, `cases`, `finish`) folds a member's outputs into a certificate as they arrive, for a member that produces them asynchronously. `certify` is now a loop over it and gives the same certificates. - **Corpora.** `Corpus::Random { seed, cases }` is the existing random corpus (`random-v1`). `Corpus::Fabric { seed, cases, wide }` (`fabric-v1`) continues the same stream after it with circuits of 11 to 17 qubits (`lanes::random_wide_circuit`), so that every dispatch shape a 32-bit-lane member uses is exercised. `FABRIC_DEFAULT` is seed 7, 200 + 24 circuits; `GENERATOR_FABRIC`, `GENERATOR_FABRIC2` and `fabric_generator` name it per gateset, and `Corpus::{generator, name, circuits}` describe any corpus. The fabric-v1 lanes32 digests are pinned: `41c726a0…3df7` (circuit/1) and `9a84b55a…7809` (circuit2). `lanes::certify_corpus` certifies the lane core on any corpus; `certify_lanes` is it on the random corpus, with the same generator string. - **The lane core, word by word.** `lanes::GateWords` and `gate_words` (a gate as the eight 32-bit matrix words and the target and control masks a kernel reads), `fxmul_wide` (the exact 64-bit `fx`), `PROBE_EDGES`, `apply_words` and `run_words` (the general 2×2 path computed from exact 64-bit values, flagging a gate exactly when the reference's result leaves 32 bits, and refusing malformed words), `simulate_checked`, `output` and `reference_output`. `lanes::horizon(n, gs)` is the gate count below which a valid circuit provably cannot leave 32 bits, computed in integers from the gateset's table: 46,340 (circuit/1) and 46,339 (circuit2) at 26 qubits. The module documentation and `fxmul` no longer claim that every result of a valid circuit fits in 32 bits; it holds within the horizon. - **`quantum_fabric::plan`**: `Program` (one step per op, the start reduced modulo 2^n), `PlannedGate`, `Step`, `compile`, `LaneRun` and `execute`, the CPU executor a member's output is held to; `PLAN_REVISION` is 1. - **`quantum_fabric::wgsl`**: `SHADER`, the lane core transcribed into WGSL (`src/quantum_fabric/lanes32.wgsl`: unsigned 32-bit values only, an exact 64-bit copy of every result, literal shift amounts, no float, division, conversion, workgroup memory or barrier); `implementation_id()` (`wgsl-lanes32/dc8e19f61e0e87ab`, a BLAKE3 over the shader text and `HOST_LAYOUT`, so any edit is a new implementation that earns its certificates again); `gate_record`, `Grid`, `grid` and `gate_grid` (the 64-byte gate record and the dispatch grid; `WORKGROUP` and `X_GROUPS_MAX` are 64); `AdapterIdentity` with `member_id` (`local/gpu-…` or `local/swgpu-…`, sorting after every `local/cpu-`), `key` and `class`; and `MUTANTS`, `mutate` and `Detector`, the shader mutants every device must catch. The module documentation carries the argument that the kernel is bit-exact. `wai/tools/quantum-wgsl-check` (unpublished) holds the shader to that argument's rules on naga's IR and translates it to MSL, SPIR-V and HLSL. - **The WebGPU member, feature `quantum_fabric_webgpu`** (opt-in, not in `full`). The kernel through the platform's GPU interface natively (Metal, Vulkan, DX12) and through a browser's WebGPU on `wasm32-unknown-unknown`; wgpu 30.0.1 enters only those two target tables, and on every other target, `wasm32-wasip2` included, the module is compiled out. `discover()` opens each adapter at the WebGPU downlevel limits with only the buffer limits raised, pins the DX12 shader compiler (translator `naga+dxbc`), probes the device's arithmetic against the lane core, and lists each adapter it cannot use with the reason (`Discovery`, `Refusal`). `WebGpuMember` runs lanes, programs and circuits, certifies a gateset on a corpus, and builds a fabric `Member`; a bit-exact member reproduces the pinned random-v1 and fabric-v1 digests. A result that would leave 32 bits is refused (`WebGpuError::OutOfRange`), never returned. Every entry point is async with no executor; natively `blocking` resolves each call in one poll, and a future still pending is an error. Natively each submission is waited on before the next, so `POLL_TIMEOUT` bounds one chunk of gates, not a run; a failed wait, which the GPU interface reports by panicking, is caught and returned as `WebGpuError::Device`, and the member treats its device as lost from then on (a `panic = "abort"` build still aborts). With no energy figures, placement keeps the CPU reference, and a receipt over a member's run is unmetered. - **wai-rs.** The feature `quantum_fabric_webgpu` forwards to it and re-exports the module. `wai_quantum_vectors verify --webgpu` runs the corpus check once per WebGPU member, each state computed by the member; each accept file must also run on the member as it does natively, so the 255-qubit files are refused as `too-many-qubits`. Without the feature `--webgpu` is an error; with no member it exits 2. - **CI.** `wai-quantum-webgpu.yml` checks the shader under naga and a SPIR-V validator, the browser build, the feature's absence from the `wasm32-wasip2` dependency tree, the shader file in the package, and the MSRV; and runs the member's tests (`scripts/wai-quantum-webgpu-member.sh`) on software Vulkan, Metal, and a discrete GPU through Vulkan. Each member job declares the adapters its runner must have, and fails if the adapters found differ, if any test did not run, if a declared adapter lacks a bit-exact certificate for each pinned digest, or if a shader mutant survives. The `wasm32-wasip2` job requires the fabric's new unit tests by name. ### Packaging: refinement layers, stills, a piece's ladder, live windows and delivery - **Refinement layers in packages** ([packaging](extensions/packaging.md) §5.4). For each refinement rung of a piece's ladder the packer writes a `wai.video.int_refine` object over the piece's `wai.video.int_motion` base: the manifest `wai stage refine` writes, and a `WIR1` from `wai-rs`'s refinement encoder (`staged::wir1::encode`), one unit per frame, toward the source from the stage below as a sink reconstructs it. Each layer is applied to the decoded base by the reference sink before it is kept; the index states each prefix's tier, digest, SSE and deadline. - **A piece's ladder, and stills** (§5.7). A rule's `then.ladder` chooses the rungs a piece is coded in; otherwise the component's first base rung, its refinement chain and its floors. §3.4 item 10 now requires a rule's ladder to name exactly one base, every refinement over a rung it names, and every floor of its component. A base of `wai.image.png` is a still: the piece's first frame, held for the piece, as a PNG every byte of which is stated (the `Sub` filter, one fixed-Huffman deflate block whose matches are stated), so an independent implementation writes the same file. - **Catalogs** (§8.3, §8.4). The MoQ catalog is now a stage set of moq-streaming-format §4.1, written by `wai::staged::moq`: `wai_staged` on every track, a receipts track and an alias per media track, `depends` on a track of every layer below; it replaces `priority` (MOQT's own, §8.5) and `wai_layer`, which the registry no longer lists. Over HTTP, whose variants cannot mix bases with no capability in common (http-delivery §4.2), a still's piece is played from the floor; its stage view and MoQ tracks list it. - **Live windows** (§9.2). `wai-pack live` packs a Y4M stream from standard input as it arrives, with the coder the on-demand packer uses, in windows sealed by version-2 receipts chained `live-window`; its bases and refinements are the on-demand package's, byte for byte. `wai-pack listing --primary` lists what the two share. It writes as it goes: each object when it is published and each window when it is sealed, every file whole, and the plan file and `LISTING` at the end. It does not keep an object's bytes once written, or a record per file: each listing line goes to a hidden file and is sorted into `LISTING` at the end. A source that fails mid-stream keeps every sealed window and the objects it lists, removes the rest, and exits 1 saying what it kept. `wai::pin::listing_from_sha256` writes a listing from per-file digests. - **Stills read by PNG's rules** (§5.7). Both reference readers take a PNG of 8-bit RGB without interlace and agree on what that admits. They read a suggested palette, ancillary chunks and split image data. They refuse an interlace, bytes after `IEND` or after the zlib stream, a short row, a zlib window past 32 KiB, and every other chunk fault (`stills/`). Both inflate as the zlib library does: no over-subscribed Huffman code, and no incomplete one but a lone one-bit code. wai-pack's reader has its own inflater, and `png_diff.py` runs it against `pack_verify.py`'s zlib. - **The longest piece** (§3.4, §4.4). `max_ms` is at most ten minutes, and at most `2^24` units on every component's timebase (`policy-piece-bounds`). A verifier refuses a piece longer than the policy's `max_ms` before it decodes a frame. It checks each floor object's unit count before decoding (§11.3). It hashes a still's frame held for its piece without repeating the frame. - **Manifests checked by both verifiers** (§11.3). A base's manifest must be §5.2's and a refinement's §5.4's (`object-manifest`, a new reason). A base that does not decode is `prefix-digest`. - **A repeated object over HTTP** ([http-delivery](extensions/http-delivery.md) §4.2). A repeated object contributes once, with its layers, and a layer joins every occurrence of its base. `http_verify.py` and `wai-rs` now agree on a playlist's codecs and floor where a piece repeats. `pack_verify.py` checks every golden playlist with `http_verify.py`'s union, floor and adjacency checks. - **One stall bound.** `stage_measure::sim::stall_bound_for` is the only bound: a model's, which takes each refinement's own timeout. wai-web's simulator prints it, as the native tool does. - **Delivery, simulated** (§8.9). `wai_pack::delivery` and `wai-pack sim` run staged-measurement's simulator over what a live packer publishes and what a class subscribes to: per-track delivery timeouts, §8.5's priorities, the subscriber's cap, group receipts on each track's chain. The simulator's model gains a refinement's own `timeout_us`, and §10.4's bound takes the longest refinement timeout ([staged-measurement](extensions/staged-measurement.md) Appendix B); its corpus adds a per-track model. - **The corpus**: the golden `video-staged-01` (a moving shot, a bumper played twice, a still shot, a second moving shot; two refinement layers, a still, the event's deadline, three classes), its supplied floor and how it was written (`supplied_floor.py`), `staged-mutations/`, `delivery/`, `stills/` and `live/` (the golden packed live in windows of two pieces, whose chain `pack_verify.py` checks). `stage_diff.py` runs refinement layers, stills (wide ones among them, so every deflate distance code is used), piece plans and deliveries differentially against `pack_verify.py`, which re-derives every layer and still from the source byte for byte. `sim_diff.py` runs the delivery simulator differentially against `sim_verify.py`. `pack-refinement-unsupported` and `pack-rule-ladder-unsupported` are gone. - The base golden `video-base-01` keeps its objects, index and receipt; its MoQ catalog and its `LISTING` change. ### The record made true: training, paths, protected content, reader limits, security - **`int_synth`'s weights are quantised after float training.** SPEC §5 and [determinism-tiers](extensions/determinism-tiers.md) §5 said the capability decodes "QAT weights". No committed model was trained that way: the trained corpus vector, `photo`, was made by `tools/wai_int_synth_real.py`, float training then post-training quantisation with each layer's shift derived from its measured activation range, and the weights of the synthetic vectors and of the cross-architecture golden come from a seed. Both texts now say so, and so do the tools: `tools/wai_int_synth_qat.py` is marked experimental (it trains against one fixed shift and made no committed model), and `tools/wai_int_synth_export.py` names the working path. - **Stale paths.** The int_synth decoder is `int_synth.rs` at the crate root, not `neural/int_synth.rs`. The quantum-ops engine and the quantum-sim receipt live in `wai-quantum`, whose features `wai-rs` forwards. README and SPEC Appendix A no longer send macOS builders to a `.cargo/config.toml` that no longer exists: `wai-rs/build.rs` finds libFLAC. - **Protected content: two lanes** (SPEC §6.5). Lane A, hardware-protected content, decodes inside the platform's protected media path; WAI's records about it (among them the served manifest, the segment map, the key-release decisions, the ad decisions and impressions of `wai.video.ssai`, the program-as-run windows of `wai.video.channel`, and the content hashes of WAI objects) cover no decoded frame. This revision registers no `security.encryption` value for it. Lane B, end-to-end encryption at the application layer (`mls-sframe`), decrypts in the application, so the tier and the output digests of the sink that holds the key hold as they do for plaintext; staged-measurement counts no encrypted object, and a verifier without the key checks none of a `reconstruct-session` claim's rules that need a payload. Lane B is not a protected-path robustness level. `wai.video.keys` and the confidentiality extension say which lane they belong to. - **Where the exact tier holds** (determinism-tiers §5). `decode-equivalence` is shown on native CPU and on wasm32, on no GPU or NPU; a WebAssembly decoder at the tier uses no relaxed-SIMD instruction and depends on no NaN's bits; and the tier never covers output a sink cannot read. - **Reader limits** (SPEC §2). A reader need accept no manifest longer than 2²⁷ bytes (128 MiB) or nested deeper than 127, and refuses a longer one from `man_len` alone (`manifest_too_large`). The length limit is sized to the largest manifest a conforming staged layer has: a `prev` (staged-delivery §3) naming a different producer for each of the 2²⁰ units a `WIR1` layer may cover (staged-delivery Appendix A.3) is about 94 MB. It bounds what a reader reads, not the memory a parse takes, which in the reference readers is up to nearly 40 times the manifest's length (`SECURITY.md`). `wai-rs`'s readers, its HTTP head parser (`head_malformed`) and the Python verifiers apply the length limit, and the HTTP corpus holds it at its edge; the writers, `wai-web`'s included, refuse to write past it, and refuse a payload whose length does not fit its `u32` field. *Envelopes this makes non-conforming:* none of the layout's; an envelope whose manifest is longer than 128 MiB, or nested deeper than 127, is one a conforming sink may refuse, so a conforming encoder no longer writes it (SPEC §7). - **`SECURITY.md`.** How to report a vulnerability, and where the reference implementation reads untrusted input: the manifest limits and the memory a manifest's parse takes, the native codec libraries and their gaps (unbounded decompression output for `wai.text.zstd` and `wai.text.xz`; no fuzzing in CI), module authorisation, and the fingerprinting risk of `held_sets`. CI checks its section references. ### Vector graphics (Draft): the extension text of `wai.graphics.vector`, §1–§3 - **A new extension, [graphics-vector](extensions/graphics-vector.md) (Draft).** `wai.graphics.vector` carries titles, credits and other authored graphics as integer drawing instructions, which the sink rasterises into an overlay layer. This revision writes §1–§3: overlay-equivalence and text-equivalence, which cover the overlay layer and the computed text and nothing after them; the `WVG1` payload and the `WOS1` outline-set format, with their canonical rules, caps and the order in which a sink refuses, including a per-frame cost each listed size states and a sink verifies at load; the digest sidecar; and the arithmetic, as the raster stage family RS1–RS9, in which no value wraps. Outline sets, text and accessibility, and packages follow in later revisions of the Draft. Nothing is registered: the string is registered once its conformance corpus is complete. ### wai-web on older engines: the reach builds - **Three builds from the shipped package, and a loader** ([wai-web/reach](wai-web/reach/README.md)). `reach/build.sh` derives them from `wai_web_bg.wasm` and `wai_web.js` with no change to the Rust: - **L1** legalises the binary's JS interface (`wasm-opt --legalize-js-interface`), so engines where i64 does not cross as `BigInt` run the full module; - **L2** lowers it to MVP WebAssembly and legalises it; - **J** translates the MVP module to ES5 JavaScript with `wasm2js`. L2 and J come in profiles, kept by `wasm-metadce` from the exports each needs: `full`, `verify_decode`, `verify` and `receipt`. An ES5 runtime supplies the missing builtins and joins and splits i64 values at the boundary: a `BigInt` where the engine has one, a `Number` (exact below 2^53) where it does not. The glue is the shipped glue compiled to ES5, with counted changes. One of them rebuilds a memory view whenever the memory's buffer changes, not only when it is detached. - **`wai-load.js` chooses by feature probes alone.** Tiny modules passed to `WebAssembly.validate` (MVP, sign-extension, non-trapping float-to-int, bulk memory), one instantiated module that returns its i64 argument, and checks for typed arrays decide between shipped, L1, L2 and J. The probes never throw. An early WebAssembly engine line (WebAssembly and the stack-trace capture API, but no RegExp `dotAll` flag: the feature `noDotAll`) gets J, or the L2 receipt build, because the full MVP module has crashed an engine of that line on arm64. A rung can be forced, and the loader says why it chose. - **Pinned and recorded.** binaryen `version_123` is checked against each platform archive's SHA-256, and TypeScript, esbuild, acorn and binaryen's WebAssembly build are pinned by `package-lock.json`; J's `wasm2js` is that WebAssembly build, so J comes out the same on every host. `reach/manifest.json` records the SHA-256 and sizes of every output built from the package the site serves, and the commit that package was built from; `reach/record/` keeps that package. Given it, the build refuses any output that differs. - **Checked output for output.** - `reach/test/conformance.js` (ES5) runs workloads over the repository's corpora on any rung, and every output must equal the shipped build's. The workloads cover receipts, the staged corpus, the integer payloads, int_synth, the menu, the output tree and the i64 boundary. - `reach/test/run-modern.mjs` forces every rung and profile on a current Node, and runs the demo verify scripts against each full build. - CI (`wai-web-reach.yml`) runs a leg per package. On the package of record, kept in `reach/record/` and held to the manifest's SHA-256, every output must equal the manifest's; on the tree's own wai-web, the reach builds must still build and pass. Each leg runs the conformance on the Node images of the engine lines each rung serves (J on `node:0.12`, the early-engine routing on `node:8.0.0`, L2 on `node:8` and `node:10`, L1 on `node:12` and `node:14`) and the shipped build on a current Node. The images stand in for device classes, which the README names by capability. No real old device has been run. - **The receipt vector's signer id is a label.** The receipt workloads verify an asset and its provenance receipt in `reach/test/vectors/provenance/`, signed with `wai_prov`'s published test seed under its label `did:example:wai-prov`. `wai_prov --carry ` measures nothing: it carries each step's figure, and its label, from an earlier receipt for the same lineage and signs them under this revision's id, and it refuses a receipt that does not verify or whose steps are not this run's. The vector carries the figures `wai_prov` measured when it first wrote the asset, and the documented command, carrying the receipt onto itself, writes both files again byte for byte. ### Generated pictures and scores, made natively and checked by SHA-256 - **The payload of `wai.generate.spectral`** (SPEC §5, *Payload of `wai.generate.spectral`*). The registration is completed (§8): the payload is an instruction object of form v0, one canonical JSON object naming the model by its SHA-256 and giving the prompt, the seed, the steps and every size a sink makes. One serialization per object, so an envelope's content hash names the instructions; a sink makes only listed sizes, with that model only, and checks each output by its SHA-256, never by a 64-bit FNV-1a alone. The intent of an envelope that names the generator is `create`, and an envelope with another intent is non-conforming. The model is the generator's parameter set (§3.1) and `model` its identity, so an envelope that names the generator carries no `prior` pin, and a sink refuses one that does (§3.1 says so beside the pin rules). `wai inspect` and `wai unwrap` report the payload, the intent and any pin of such an envelope, ✓ or ✗ with the reason. Vector: `wai-rs/tests/vectors/generate/instr-v0.json`, the object, the SHA-256 at each of its four listed sizes, twelve objects at the inclusive bounds a sink accepts (each with the SHA-256 of its first listed size), and 36 objects a sink refuses, each with the rule the reference reader finds broken first, which its tests assert. - **`wai generate`** (feature `generate`) makes a picture with the reference generator at any size form v0 allows and prints its SHA-256; `--instr` makes a listed size of an object, read as a sink reads it, and `--write-instr` writes one. `sizes.json` pins the committed prompt at 256×128 and 640×320. The wasm32 workflow checks that the browser binding makes every pinned picture and score with the SHA-256 the native tool wrote (`wai-web/demo/verify_generate_wasm.mjs`), so a page can hold its own output to these pins. `wai wrap` refuses a `wai.generate.spectral` payload that is not a form-v0 object, and any intent but `create` over that generator, in every build. - **`wai score build|render`** (feature `score`). `build` writes a `WSCR` from integer note fields only (score §2.3); `render` prints `samples`, `pcm_sha256`, `mixdown_blake3` and `score_hash`, and `--pcm` writes the samples. Every `score-conformance` vector now states `pcm_sha256`, and `notes/.json` holds it in the authoring form, which `build` turns back into the committed `WSCR` byte for byte. - No library source changed: the generator, the score synth, the world floor and the browser binding are as they were, so pins made with this tool bind the binding pages already load. ### Quantum circuits: `wai.quantum.circuit2`, and `wai.quantum.circuit` stated exactly - **A new capability, `wai.quantum.circuit2`** ([quantum-sim](extensions/quantum-sim.md), SPEC §5). Magic `WQC2`, contract `ext` `wai.quantum.circuit2/1`, numeric `wai.det.amp64`, gateset `cliffordT+dyadicP.rn`. The container layout, the gate IR and the arithmetic are those of `wai.quantum.circuit`; the phase table is not. Each `P(k)` holds the integers nearest to `2^30·cos(2π/2^k)` and `2^30·sin(2π/2^k)`, so its controlled-phase ladder gives the QFT. The literal table is normative (§3.2, Appendix B.2, digest `d34cb793…3510`); an exact-integer interval procedure certifies every entry, informatively. The contract and the measurement are byte-canonical, and the op-log ends exactly after its last op. Registered in the machine-readable registry and the vocabularies, with `wai.det.amp64` as an identifier. - **`wai.quantum.circuit` is frozen, and its table stated for the first time** (§3.1, Appendix B.1, digest `3fa9805f…8466`). The text gives the exact recurrence — the sine formula, the truncating halving, the floor square root — and the deviation of every entry from the ideal phase: from `P(8)` on the imaginary part is too large, by up to 7032 units of `2^−30`, and `P(19..32)` apply no phase. The claim that this table gives the QFT is withdrawn. Writers SHOULD NOT emit new circuit/1 content. - **Both capabilities are specified to the bit** (§2–§4): little-endian integers, section bounds computed without overflow, a repeated known section refused, the contract and the measurement read as I-JSON (RFC 7493) by rules stated in full: no repeated member name, no surrogate or noncharacter, no number of magnitude 2^1024 − 2^970 or more, at most 127 levels deep, and an integer read exactly; the three refusal classes (malformed, contract, invalid) in the order a reader checks them, the complex-product composition, the index convention, the gate matrices, the hash layouts and the shot sampler. `circuit_hash` covers the canonical contract, so it names the capability and its table; `statevector_hash` does not. §5 states the error per capability, and a new §8 why circuit2 is a capability of its own and what an older reader does with it (it refuses `WQC2` at the magic). - **Newly non-conforming circuit/1 payloads** (quantum-sim §8, SPEC §5): a contract whose `ext`, `numeric`, `frac` or `gateset` is missing or carries another value; an `n_qubits` that is not an unsigned integer of at most 255; a known section kind more than once; section bounds that overflow; a contract or measurement that repeats a member name within one object, holds a noncharacter, or holds a number of magnitude 2^1024 − 2^970 or more that an earlier build's float parser let through (some builds also refused numbers just below it, which are now accepted). No file a reference writer wrote is among them. - **Corpus.** `quantum-conformance/` keeps its four original vectors and manifest entries byte for byte and adds qft5_x22, dyadic_ladder and qft8_ones, which see the phase table: under either table, `dyadic_ladder`'s statevector hash changes with every ±1 change of any `P(1..32)` component. `v2/` holds the same seven circuits as circuit2; `reject/` at least one payload for each refusal a payload can carry, with its class; and `accept/` payloads at the edges of the same rules that a reader must accept, with how each reads. `tools/referee.py`, integers only, derives both tables, requires Appendix B to state them, reads every JSON section by the same rules, and rebuilds and checks every file. The Appendix B tables are rendered from the reference constants by `wai_quantum_vectors appendix`, and a test fails when the text differs. ### wai-quantum 0.4.0: circuits carry their gateset - **`Gateset { V1, V2 }`** names the capability a circuit is reconstructed with, and `Circuit` carries one in a new field. `Circuit::new` and `Circuit::qft` build V2 (`wai.quantum.circuit2`); a circuit read from a container takes the container's, and one derived from another keeps it. `PHASE_V1` and `PHASE_V2` are literal constants: no recurrence runs at load. `simulate`, `to_wqc`, `circuit_hash`, receipts, the fabric's lanes and the toolchain follow the circuit's gateset. `from_wqc` reads both magics, checks a circuit/1 contract's values and a circuit2 contract's bytes, reads the contract and the measurement as I-JSON with a strict reader of its own that decides every rule from the text, the number range on the literal's digits (so neither a build feature nor a float parser's limits changes a verdict), refuses repeated sections and wrapping bounds, and no longer sizes an allocation from a claimed op count. `reject_corpus()` and `accept_corpus()` return the payloads a reader must refuse, each with its class, and must accept, each with its reading; the corpus's `reject/` and `accept/` trees are written from them. - **Every circuit built in code changes its `circuit_hash`**, since the contract names the capability, and its statevector wherever `H`, `T`, `T†`, `P(3)` or `P(k ≥ 5)` acts on a nonzero amplitude (`P(1)`, `P(2)` and `P(4)` are the same in both tables). Every circuit/1 artefact reproduces: the four original vectors, the legacy receipt pins, the lanes32 certificate corpus and the other pins are unchanged, built in `Gateset::V1`, and new V2 pins sit beside them. - **wai-rs** registers `wai.quantum.circuit2`; `quantum_envelope` tags an envelope with the circuit's capability and refuses one whose capability and magic disagree. The `wai-quantum` CLI takes `--gateset v1|v2` (default v2) and names the capability in its JSON; with `--gateset v1` and a phase of `k ≥ 8` it prints the circuit/1 deviation on standard error. `wai_quantum_meter` seals the capability the file names. The browser sink reads both magics; the IDE has `set_gateset`, and its `add()` sets the parameter of every gate but `P` to 0. The serve component, `wai-quantum-serve` 0.5.0, takes `gateset=v1|v2` on `/run`, `/qasm` and `/receipt`. - **Migration from 0.3** (a breaking release): - `Circuit::new` is V2. To reproduce or verify a 0.3 artefact, read its `WQC1` file, or set `c.gateset = Gateset::V1` on a circuit built in code; a receipt's `gateset_of` says which gateset a circuit's ops match it under. - `quantum::phase`, `quantum::inv_sqrt2` and `quantum::CAPABILITY` are removed in favour of `Gateset::phase`, `Gateset::inv_sqrt2` and `Gateset::capability`. - `to_wqc`, `circuit_hash` and `quantum_toolchain::circuit_id` return a `Result`; they fail only on a circuit its gateset does not allow, never on one that has simulated. - `Circuit` and `QuantumError` are `#[non_exhaustive]`; `QuantumError` gains `Contract` and `kind()`. - `quantum_fabric::certify_lanes` and `lanes::random_circuit` take a gateset, with a kernel and generator id per gateset; the circuit/1 ids are unchanged. - `validate` refuses a gate with more than 255 controls, and in V2 repeated controls and a parameter on any gate but `P`. `from_wqc` refuses the payloads quantum-sim §8 lists. ### Integer operators (Draft), and the payload text of two integer codecs - **A new extension, [integer-operators](extensions/integer-operators.md) (Draft).** `wai-ops/1` is the integer operator set the integer-exact capabilities decode through: each operator's exact arithmetic over the integers, with rounding, bias placement and shift scope as parameters of requantisation; a range policy on every tensor (`derived`, `clamp` or `check`); the `acc64` and `acc32` accumulator profiles; static bounds rules, saturating at `±2^255` so that a 512-bit integer holds every value they form, and a canonical bounds certificate that decides which profile a parameter set meets; the accelerator subset `wai-ops/1-npu`; the `WHG1` graph form; the stage operators of the drivers, among them the mANS decode of `wai.image.jpegai` (E8) with its scale path (§13.6.1); and canonical lowerings of the existing network forms (`int_synth.json`, `WHM1`, `WAM1`, `WMB1`) to `WHG1`. The reference crate, the tool and the conformance corpus it names follow this Draft; until they land, each capability's reference is the decoder its payload extension names. - **[integer-payloads](extensions/integer-payloads.md) writes down two more formats.** §13 is the `wai.neural.int_synth` payload and its set, `int_synth.json`; §14 is the `WIM1` payload and the `WMB1` bundle of `wai.neural.int_mlicpp`, including that the entropy network reads every element of `y` not yet decoded as 0, and that the three networks and the tables share one `afrac` in `1..=30`. §9 gains the members and checks of `entropy.json`, and states that the layers' arithmetic is integer-operators §13.2 and §13.3. No decoder changes: §13 lists the `wai.neural.int_synth` checks the reference decoder makes once it decodes through its lowering. ### 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. - **Receipts, keys, grants and parameter sets** (http-delivery §8.1–§8.5, §10.5): - a receipt file `rcpt/.json` holds one group receipt (in the labelled payload) or claim in JCS, at most 1 MiB, read as jwp-receipts reads one and refused unless its link hash is its name (`receipt_name_mismatch`, `receipt_not_served`, `receipt_too_large`); a legacy group, whose name does not cover its parent, is refused (`receipt_legacy_group`); - an index, at most 4 MiB with at most 256 delivery items and 256 claims (`receipt_index_too_large`), is read item by item (`receipt_index_malformed`), each receipt file fetched once per index, at most 1024 in all (`receipt_fetch_budget_exhausted`) and 64 MiB read in all, each file read no further than what remains of that (`receipt_fetch_bytes_exhausted`), each cap with its reason stated: each delivery item's group goes through jwp-receipts §3 against the client's chains, a missing parent fetched by its link up to 64 of them, never more, and never skipped (`parent_not_held`, with where the walk stopped), a joining client anchoring instead; its object receipt through steps 4 and 5 against what the client holds — the hash recomputed over the envelope, the cid a head proof established (§8.3), or nothing, which is `inclusion-verified` and gives no property reading (§10.5); each claim by its subject against the items that passed, its parent fetched by its link, then §3.1 step 6 against what those items deliver and the receipts it names (`figure_not_held`, `context_not_held` where the client does not hold them; an energy claim naming an OER receipt, which this revision does not carry over HTTP, is `context_not_held`), a declaration it names read as §8.1 reads every file fetched by name before step 6 compares it, a recovery record checked with the client's matchers whether or not it holds the object, and a soft binding of another object attesting the ancestor only through §3.2's recovery, under the held publisher key that signed it; - a key log anchors only from the origin's own location over HTTPS, answered `200` with no redirect (`key_log_not_from_origin`), from a retrieval the verifier made itself, asking with `Cache-Control: no-cache`; a key log is read to 1 MiB and a longer one refused, anchored or cached (`key_log_too_large`); a log through any cache adds revisions and never moves the anchor, an older copy reported (`key_log_rolled_back`); a grants file is a locator (`grants_file_malformed`); a key status is served for a quarter of the head's `status_max_age_s` at most; a module grant is §7.9's form, and a JCP grant is carried only by reference in the receipt that names it; - a parameter set served by its digest is held only by its pin and §7.9 (`pin::verify_bytes_with`, `int_payload::resolve`), never by its name. - `wai-rs`: `http_binding::receipts` and `http_binding::keys`; `jwp_hash::EstablishedHash`, built only from bytes or by a fetch session whose head proof verified (`FetchSession::established`); `ObjectReceipt::accept_hash` and `verify_against_hash`, `WaiReceiptProperty::check_against_object_hash` and `ReceiptChains::verify_admitted_object`, which the existing checks call; `energy_figure::HeldObject`, an object receipt's figure for step 6; `wai http receipts`, which fetches under a deadline and the caps above, follows no redirect, and with `--key-origin` retrieves the key log from the origin itself over TLS (the `cli_tls` feature: a TLS client validating against the operating system's trust store; SNI and the certificate check name the origin's host, port 443 by default, each address the host resolves to tried in turn), every step within one deadline however slowly a peer answers; a build without it refuses to anchor rather than fetch the log over plaintext, and the test-only `--test-plaintext-connect` anchors with its own path, `test-plaintext`, wherever the anchor or a confirmation is reported, as anchoring nothing a verifier may rely on. A group or object receipt whose `kind` is not a string is `wrong_kind` (jwp-receipts §3 step 1), as the independent verifier already read it. - The corpus serves receipts, indexes, a key log, a status, a grants file and a set; `http_receipts.py` checks them for `http_verify.py` through the jwp-receipts, claim and key-discovery verifiers, and the differential adds receipt files, indexes, grants files and key logs. - 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 `-