open-standards/wai/CHANGELOG.md
Transaction Science 0077d74c6c
Some checks failed
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 1m54s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 3s
no-vanity-metrics / WAI public text names no comparison (pull_request) Successful in 3s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 2m7s
no-vanity-metrics / WAI public text names no company or product (pull_request) Successful in 3s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m9s
standards workspaces build + test / aac-rs (pull_request) Successful in 1m18s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 3m36s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 48s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 32s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m55s
standards workspaces build + test / map-rs (pull_request) Successful in 28s
standards workspaces build + test / mesh-rs (pull_request) Successful in 23s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m33s
standards workspaces build + test / openpay (pull_request) Successful in 1m29s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Successful in 22s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m8s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m10s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 5m38s
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Successful in 29s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m16s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m17s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m6s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 8m7s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 35s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m39s
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Successful in 27s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 2m41s
wai-pack / wai-pack · linux-arm64 (pull_request) Failing after 59s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 6m36s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 13s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 12m59s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 8s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 24s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 45s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 1m40s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 31m50s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 2m3s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 10m46s
wai-pack / wai-pack · macos (pull_request) Failing after 16s
WAI HTTP delivery / HTTP corpus · falsification and differential (pull_request) Successful in 1h19m18s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 1m4s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 1m5s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m10s
wai-pack / wai-pack · linux (pull_request) Failing after 15s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Failing after 55s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 20s
wai-pack / wai-pack · dependency licences (pull_request) Failing after 1m8s
WAI section references / cited sections exist (pull_request) Successful in 10s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 4m22s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Failing after 2m24s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 3s
wai: state the ranged-WAI2 classical length as a limitation of this revision
jwp-receipts §2.4.12 (classical_declared and rule 9), the CHANGELOG and the
claim-conformance README (the envelopes context and the sweep table's SPEC §6.4 row,
the one that cites rule 9): this revision derives the classical length from the WAI2
envelopes held in full; a sink that received a WAI2 only as byte ranges holds its head,
not the envelope, and cannot yet state its classical rendition. Recording such a
session is HTTP session work still to come.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 05:23:56 -04:00

254 KiB
Raw Permalink Blame History

WAI — changelog

Notable changes to the WAI standard and the reference implementation. Newest at the top. WAI is a draft standard; until v1.0 freezes, the wire format may change and such changes are recorded here.

v0.4.0 — current

Staged measurement and the session claim in the browser

  • The measurement core, bound for JS (staged-measurement). 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 §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 §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.
  • 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 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 §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'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 §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 §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 §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 §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 §10.5 says what an abandoned refinement is: inclusion-verified, and dropped or late in a stage claim; staged-measurement §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 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 (<cid>.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);
    • 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/: 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/: 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/<manifest_hash>.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/<receipt_hash>.json, the head fetched again is unchanged, and a walk is bounded (playlist_receipt_parent_missing, playlist_receipt_walk_exceeded); no revision lists another cid for an alias or a media sequence number an earlier one bound (playlist_listing_rebound);
    • Ed25519 is verified strictly (wai::sig::verify_strict: a canonical key of large order, S < L, R of large order compared byte for byte), by the playlist receipt and http_verify.py;
    • DASH adjacency is checked over an XML subset (Appendix I); wai.mpd takes the classical MPD's period, timescale, t and d and leaves a gap out; MPD values are escaped and checked as XML;
    • adaptation states have stated bounds (abr_state_out_of_bounds) and are computed exactly within them; under a budget only a labelled option within it is chosen;
    • a gap segment carries no parts or layer lines; the session-data line has one place in the classical master; a receipt file holds no member twice, no lone surrogate and no number past 2^53 − 1, a grant of no joule ceiling written null; seal-playlist --grant; the origin answers a far-future _HLS_msn with 400.
    • an MPD is held as written: timelines in runs, never expanding @r, compared run by run, at most 256 per document, and an element's attributes read in linear time (Appendix I);
    • a budget never raises the policy's choice: with nothing at or below it within the budget, option 0 and the budget unmet (replacing floor);
    • past the walk bound, a revision ahead of the head re-anchors the chain (reanchored), keeping its bindings, the revisions between unsigned; bindings key a line by its URL resolved against the playlist's (RFC 3986), a gap never withdraws a listed segment, and a binding is kept while one of the last 256 revisions lists it.
  • What review found, fixed (http-delivery §3.1, §4, §7, §7.1, §10, §13, Appendix D):
    • a layer object (a WAI1 whose capability is refinement-only) is never dispatched: it is fetched whole at refinement urgency, verified against its cid and handed to staged delivery, and joins a union after + through refines.base (not_a_layer, codecs_layer_unbound, layer_tag_malformed). A WAI1 whose capability is a payload is an ordinary object even when it carries refines: it is dispatched, described by its capability, and not_a_layer when listed as a layer. A layer is declared only in the alt of its own base's capability;
    • a refinement never moves its base: only base-class bytes count toward the 90 % rule, a refinement is always requested on its own, a refinement with no digest or a failed one is dropped alone on the range path and on the whole path alike, and a refused refinement response abandons only it;
    • a client presents a base as soon as it verifies and applies its refinements as they arrive; a refinement's deadline runs from that moment on the caller's clock (FetchSession::base_ready, tick), and wai http bench reports the time to the verified base apart from the time to the complete fetch;
    • after a component mismatch every byte past covered_len is discarded before the rest is fetched again; a base request never answered is base_unanswered;
    • Content-Range numbers are ASCII digits only; an element beginning wai in any case must be a well-formed WAI element; an empty element or a capability named twice is malformed; a WAI1 naming a record beside a payload fallback is described by its fallback; a codecs value with non-ASCII text, and a head proof whose lengths reach the end of 64 bits, are refused rather than panicking;
    • the reference origin caps what it reads of a request head (16 KiB); gives the whole head a deadline from the connection's accept (default 10 s, --read-timeout-ms), answered by 408 however slowly its bytes trickle in; bounds each connection's whole life from its accept, writing included (default 60 s, --max-connection-ms), so a peer reading a large response slowly is cut off then; serves at most 64 connections at once (--max-connections), answering 503 to one more, with a cap per peer address only when --max-per-peer is given (it listens on 127.0.0.1, where every client has one address, and a default cap would let a few reconnecting clients shut the rest out); decodes /__hits as bytes; and refuses \, : and NUL in a path. The wire-identifier test reads each name in its section's body.
    • a verified base is decided: a component that verified is never read again, and of a refinement's response only the range requested is kept, so a refinement answered 200 with the whole object, a byte changed, never reaches the base. Every response carries the caller's clock (FetchSession::on_response(…, now_ms)), the base is stamped with the time of the response that verified it, and the deadline's arithmetic saturates. The corpus's fetch_timed cases run the session on a clock; http_verify.py agrees on them. The corpus gains a case for each, and http_verify.py agrees with wai-rs on them and on a seeded differential outside the corpus, in which a refusal must match by its code and each family reports how many cases it generated and how many were refused.
  • No envelope byte or receipt byte changes for existing material.

Packaging: objects, the stage index, the floor, catalogs and the package receipt

  • Objects (packaging §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, 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/ 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 §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: 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 (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: 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/: 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: 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/: 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 §5 requires that telemetry MUST NOT restate the per-object property's figure, class or evidence; energy-exposure-mapping 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): 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 §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, 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 and vocab v2 (vocab/v2.{ttl,jsonld,shapes.ttl}, namespace https://wai.transaction.science/vocab/v2#, one IRI per registered string, enumeration individuals named <class prefix>-<label> so no two terms share an IRI, the registration date as registeredOn; its JSON-LD context maps a WAI1 or WAI2 manifest as written, with kind and a component's role under terms of their own; its SHACL shapes apply to a manifest with no @type and to every WAI2 component, check the capabilities against the registry, and leave media open, as SPEC §3 and §8 require). --check fails on drift; a library test and a CI step run it, and vocab/check_shapes.py validates typed and untyped sample manifests in CI. vocab v1 is stable: no IRI is removed and no term changes meaning. REGISTRY.md states the registration procedure; CONTRIBUTING.md is rewritten around it.
  • The wai binary builds without the codecs feature (encode and decode are compiled out); tests/cli_wrap_intent.rs and the new tests/cli_registry.rs run in CI. wai wrap now writes the registry's media class: volumetric for wai.mesh.*, wai.pc.* and wai.splat.* (was model), image for the learned image codecs such as wai.neural.bmshj2018 (was binary). SPEC §3 lists the media values, informational. Without codecs, inspect, capabilities and unwrap claim no native decode.
  • Editions (SPEC §8): a registration that names no edition carries the edition of its standard current on the date it was registered, and no later edition or amendment, which takes a new string. wai.pc.gpcc and wai.pc.vpcc therefore do not carry the Gaussian-splat amendments of ISO/IEC 23090-9 and 23090-5; Enhanced G-PCC (ISO/IEC 23090-38) is not a wai.pc.gpcc payload. Which edition of each part was current on their registration date is unresolved, and what they admit beyond the part's first edition is unspecified until a revision records it.
  • SPEC: wai.world.replay, wai.world.live, wai.semantic.clip and wai.semantic.perco2 are spelled out or given rows, and a test holds SPEC and the registry to the same strings in both directions; the opening, §1 and the floor paragraph agree with §5; citations: PNG (W3C Recommendation, Third Edition), FLAC (RFC 9639), Zstandard (RFC 8878), AVIF (the AOM AVIF specification, HEIF-based, MIAF-conforming), JPEG AI (ISO/IEC 6048-1:2025); the neural tables state each model's published operating point; Appendices B and C are rewritten; confidentiality's implementation is a companion implementation under its own licence.
  • The codec glossary links each registered capability that has an entry, with the registry's licence and replicate classes, through a reviewed table (GLOSSARY_LINKS); every other payload and companion capability is listed with a reason (GLOSSARY_NO_ENTRY). org became body, from a closed allowlist of standards bodies, designations and open-source projects; new modality values text, volumetric, haptic, metadata; new entries for the registered formats that had none; descriptions scrubbed of company names and comparisons; entries titled by the format's own name, FourCC, file extension or designation, never by a company, a product or a person (the research codecs by their method); HEVC regraded success; JPEG AI dated 2025; AVS3 video's body is the designation the registry pins. The file now carries "schema_version": 2: a reader of the earlier form, which had org and no version, finds no org field in version 2. glossary_links_agree_with_the_registry holds it to its schema and the registry.
  • quantum-ops vectors regenerated with neutral ids (example:lab3-device, example:qpu-a) through wai_quantum_ops_vectors gen. Changed receipt hashes: cal_q3 7c53298929a1261959315c91f9540781b4bff0669b5f4b783becee93dbf5f8fa, cal_cz 4c6374ee05c956f928455f86326357f10d08f3466e88d569a6ef49975001f526, job_bell 08b142d29dadc466b294b89d2127f39ee9fa1aa0b6b5a4c6c5d226baf343b9e3. The wai-quantum calibration pin (receipt_pins::calibration_legacy_bytes) was recomputed by the code before the acquisition class, with the neutral id. wai-quantum's version is unchanged.
  • CI: the feature-gated test targets the conformance workflow names are declared with required-features, so dropping a feature stops cargo instead of running nothing; tests/int_audio.rs and tests/int_splat.rs now run; the registry export is checked; the glossary, SPEC, registry and vocab paths fire the conformance workflow. wai-web is built for wasm32 (wai-web-wasm.yml, and the int_hyper vectors in wai-sota-vectors.yml), and its verify scripts fail on a missing artifact when WAI_REQUIRE_SOTA_VECTORS is set or a directory is named. The Windows x86_64 legs are out of the conformance and byte-equality matrices while their runner is offline (each matrix says how to restore it). The nested wai/.github/workflows/ci.yml, which no forge reads, is removed.
  • Public text: two checks, scripts/check-comparative-prose.sh (a public phrase list) and scripts/check-public-names.sh (patterns kept private), now run in CI over wai/'s text, code, data, markup and ignore files. Both fail closed (a grep error or a pattern that does not compile exits 2), an opt-out needs a reason, and scripts/test-public-text-checks.sh proves both on every run. Without the private list a push to main fails. The list is given only to the run on a push to main, never to a pull request's code; a pull request shows a failed step saying the name check did not run. The specification, extensions, research notes, READMEs, CONTRIBUTING, the glossary and source comments name no company, vendor or product and make no comparisons; functional identifiers (capability strings, model-hub ids, OS API names, advisory ids) stay, marked. Dated research records keep their findings: corrections are dated annotations (the MoQ Transport revision, the MPEG splat stages, WebTransport's status, the October 2026 call for content); names are redacted, and comparative framing is restated, in place. In this changelog's history, names are redacted and nothing else changes.
  • For later changes: a new dispatch or selection path uses codecs::is_dispatchable; a newly registered string (for example a prior set) goes into the registry table, wai registry export regenerates the exports and vocab v2 (vocab v1 is stable and only gains terms), and the string is listed in the glossary's GLOSSARY_NO_ENTRY or linked. The quantum-ops corpus and the wai-quantum calibration pin have the values above; the pwsh steps of the conformance workflow remain, inactive.
  • extensions/jwp-receipts.md §5: the MUST is over the cases §5.1–§5.5 define; verifying against their vectors becomes a MUST once the vectors are published in the tree.

The parameter-set pin, specified; component identity; pin-aware selection

  • SPEC §3.1 defines the pin a manifest carries (conditioning.prior; a WAI2 rendition's prior): id, sha256, determinism, and the optional form, tier and supersedes. The digest covers the whole parameter set: a form-less pin is admissible only for a capability registered with a one-file set (§5 registers each capability's shape), and a set of several files is pinned through its listing, the new registered prior form wai.prior.set (StandardDefined, royalty-free, the one form listed as established exact). A sink verifies the pin before it decodes, reports a refusal with one of an ordered set of snake_case reasons (pin_malformed, form_not_registered, form_unreadable, form_required, prior_revoked / prior_not_authorised (check 5, run by a sink that holds a key log; see Key discovery, signing authority and module revocation below), prior_not_held / digest_superseded, set_malformed, part_mismatch; set_incomplete during a decode), and decodes only from the set it verified. A pin over a capability this revision does not register is verified as several files. null in an optional member is the same as absent.
  • Tier is a closed vocabulary (decode-equivalence, entropy-consistency, none): an encoder writes a registered token, a sink reads any other as none, a verifier reports it (tier_unregistered). Pins without tier keep the legacy reading of determinism, now stated normatively, so every existing verdict — tests/vectors/int_mlicpp/stream.wai's included — is unchanged.
  • supersedes is informative: it changes only the reason a sink that holds the predecessor reports, a malformed one is a static finding the sink ignores, and a successor never satisfies its predecessor's pin.
  • §4 and §7.1: a pin that does not resolve makes its capability unsupported for that envelope; dispatch continues at the fallback or the next rendition, and an inert envelope's error names the reasons: for WAI1, the reason kept for the capability before the fallback's, set_incomplete included; for WAI2, every entry's (not_selectable, missing_capability, component_digest_mismatch, a pin reason, or, under a policy that serves only sound replicate claims, the entry's replicate_* reason). A WAI1 pin applies to whichever capability decodes the payload.
  • Component identity (§7.1): a rendition entry MAY carry id (unique, never a capability string), sha256 (its payload digest, checked before the entry is decoded or applied: component_digest_mismatch) and, on a companion, companion_of_id, which binds it to one entry — an enhancement layer can now bind to one of two renditions of a base capability. id, sha256 and prior are one construct; no extension defines another per-component digest.
  • §7: replicate over a NeuralIntegerExact capability MUST carry a pin that declares decode-equivalence, in no form or in wai.prior.set. The new Digests subsection names SHA-256 for parameter-set identity, for component identity, and for the decoded output of capabilities that decode against a parameter set and of standard codecs in conformance material; FNV-1a and BLAKE3 stay as diagnostics. The vectors of such capabilities gain sha256.json sidecars; no existing vector file or value changes.
  • §8 settles how an optional member is added under the 1.x freeze, which the form entry below left open: a MINOR revision MAY define an OPTIONAL member, envelopes keep wai "1.0"/"1.1", a reader MUST ignore a member it does not understand whatever the MINOR, and an earlier reader may instead reject such an envelope (fails closed) or ignore the pin (fails open), which §3.1 states. §8 states separately the one exception this revision makes to the freeze (next entry).
  • Envelopes made non-conforming: the list in SPEC §3.1. None is committed. One kind stops parsing, the one exception to the 1.x freeze that §8 states: a manifest that carries a member this revision defines (a pin or pin member, or a rendition entry's id, sha256 or companion_of_id) with another JSON type is malformed, because reading a mistyped form as absent would turn a set-form pin into a form-less one. The envelope's wai value does not signal it. New vectors: tests/vectors/int_mlicpp/{model.sha256, stream_set.wai} (the set-form twin, written by tools/wai_int_mlicpp_set_form.py) and the pin-conformance/ corpus, with an independent verifier in Python.
  • Reference-sink behaviour: a form-less pin over a several-file capability is refused (form_required), where before it verified one file or failed on a directory; wai.neural.int_synth's pin covers int_synth.json; a digest that is not 64 lowercase hex is refused, where before it was compared case-insensitively; a refused pin falls through to a fallback the registry holds; every decoder reads parameters through neural::ParamSource, so a pinned decode reads only verified bytes (ONNX sessions built from them), and an ONNX model that keeps a tensor's data in another file, which the runtime would read from outside those bytes, is refused with set_incomplete naming that file, as §3.1 now states; bmshj2018's CDF cache is keyed by content digest and holds a bounded number of tables, least recently used evicted. decode_envelope_strict applies §7's rule to the capability it decodes through, so a sound fallback behind an unsound primary the sink cannot use still decodes; it refuses before reading a file only when every capability it could decode through (dispatchable and advertised) is refused, so a candidate the registry happens to hold does not change its reason. A WAI2 parse sums the rendition lengths in 64 bits, so a table whose lengths wrap a 32-bit sum is refused as truncated on 32-bit targets too, where it used to panic. wai inspect prints digests through pin::digest_preview, which shows a malformed one whole instead of cutting it inside a character.
  • Rust API, source-breaking (the same 0.4.0 release as form): the manifest types (Manifest, ModelRequirement, Conditioning, Prior, Supersedes, ManifestV2, RenditionMeta, Rendition, Security) are now #[non_exhaustive], with constructors and with_* builders that write the same JSON a literal wrote, so the next optional member does not break callers again; every manifest object keeps members it does not model (extra), and Manifest gains security, so a Rust round trip loses nothing. Rendition holds a RenditionMeta. Wai::dispatch_capability is replaced by dispatch_verified; WaiMulti::pick/primary take a pin verifier and return Picked (index, entry, payload, verified set, skipped entries); companions_of_index binds by id. DecodeError::PriorMismatch carries a PinRefusal, and DecodeError gains NoRendition; ModelRegistry gains held/hold/verify/resolve_verified (resolve_checked is deprecated); new decode_payload, decode_multi, decode_multi_strict. sha2 is now a plain dependency, and the new pin module needs no feature.
  • C ABI: a codec-free ffi feature carries the envelope surface (codecs implies it); WAI_ERR_PRIOR (5), WAI_ERR_UNSOUND_REPLICATE (6) and WAI_ERR_NO_RENDITION (7), with the reason first in wai_last_error; the unpack calls return the original manifest bytes, as wai.h already documented; the WAI2 table carries identity, pin and selectable; new wai_envelope_select and wai_neural_decode_ex (WAI1 or WAI2, a registry of held sets, a strict flag), which writes *out on every call, empty on failure, so out->data is always safe to free. The envelope packers build under ffi too, and tests/ffi_envelope.rs checks in CI that they refuse a candidate and that wai_envelope_select never picks one.
  • wai-web wraps wai::container instead of reduced copies, so role, companion_of, identity and pins reach JS, and exposes pick, companionsOf, verifyPin and sha256Hex; a component whose capability is not dispatchable (SPEC §4) is never presented as media, and rendition(i) and companion(i) refuse an entry whose payload does not match its sha256. Its JS-facing tests run under wasm32 in CI.
  • One dispatch predicate: dispatch_verified, pick, primary, RenditionMeta::is_selectable, decode_multi, decode_envelope_strict's early refusal, wai_envelope_select, wai-web and wai inspect all take a capability only when codecs::is_dispatchable (above) accepts it, so a candidate, a record or a derivation is never dispatched or picked, whatever its pin. An unregistered string stays dispatchable where the sink advertises it, and a pin on it is verified as several files, so it fails closed. SPEC §7.1 step 2 reads "dispatchable (§4)". wai.prior.set has its row in the registry table, so registry/capabilities.json, REGISTRY.md and vocab v2 list it.
  • Vocabulary: vocab/v1 gains the pin terms additively, and wai registry export writes the same terms and shapes into v2, with the tier list taken from codecs::TIER_TOKENS; vocab/check_shapes.py validates conforming and non-conforming pins against the v2 shapes. sharedPriorUri and sharedPriorHash are annotated as not being the manifest pin. The SHACL pin shape targets the objects of wai:prior (no class is asserted for them), and supersedes is checked by its own shape: a required digest and an optional form.
  • Open item: wai.world.model names its renderer's model by model_id, a second model-identity construct; it is to fold into the pin once that renderer is registered as decoding against a parameter set.

Energy reports, labelled object leaves, and what the signatures cover

  • Reports about a figure. Two claim classes, extensions/jwp-receipts.md §2.4.8–§2.4.9, report on one energy figure without changing the receipt that seals it. A claim names that receipt by a figure profile — the receipt's identity domain less its version byte, so the registry is the code's own (extensions/energy-measurement.md §6.1) — and its identity, and restates the figure with its class. measurement carries the bracket's values as integers with each bracket's liveness facts, the build (profile, toolchain, target) and the power reader; only the key that signed the figure may sign it, so a figure no Ed25519 key signs (an energy binding's) has no measurement claim. A hardware figure's report must name its power reader (measurement_no_power_reader), with or without a bracket, and a null bracket says the figure was not obtained under the bracketed protocol. Because only §3.1 step 6 shows the signer is the figure's, a measurement claim is accepted only against the receipt that carries its figure: checked without it, it is figure_not_held, and ClaimReceipt::attribution gives a verdict only for a claim accepted against that figure. operating-point records the requested, committed and delivered operating points, from a vocabulary named by URI, and a prediction. energy-measurement §6 makes the measurement claim the carrier of a class-bearing figure's report and lists the producers that fall short.
  • Exact report arithmetic. energy-measurement §6.2 gives the figure and the least declaration a report's integers support as exact integer formulas, at whatever width their products need. Producers seal from the same integers; computed from unrounded samples, a declaration could come out one ppm below what the published integers support. wai-rs energy_report implements it with no floating-point arithmetic, and claim-conformance/claim_verify.py recomputes the pinned brackets with Python's integers.
  • Derived, never stated. The §4 ratio and verdict, whether a commitment was met, and a prediction's residual are derived by every reader from what a claim carries. A report carrying its ratio is refused (unknown_member).
  • A live power reader. energy-measurement §3.1: a rail is live in a bracket only if it advanced in two consecutive intervals, in at least five and in at least four of every five, over at least 300 ms, in both brackets; a figure from a bracket that is not live is never sealed under a hardware class. A power sample advances only when it is above zero and differs from the sample before it, so a reader that holds its last value never advances; a reader of a report refuses, under a hardware class, a bracket whose sd_uw is 0 whatever liveness facts it carries. A frozen rail, a stale value held, one batch refresh, and one refresh published in two steps all used to give a flat idle bracket and an infinite ratio that sealed as OnChipCounter; energy_burst and energy_meter now refuse them, with fault-injection tests, and a counter meter reads its counter during a span rather than only at its ends. On a host whose reader reports a rail that does not advance, the reference meters seal unmetered and say why.
  • The unmetered marker. energy-measurement §2: an unmetered figure is not a figure of zero joules. A 0 that marks unmetered is never presented, totalled or compared as 0 J; a total over parts that include one covers only the measured parts; new forms mark unmetered with class tag 0. quantum-sim, provenance, interactive-worlds, moq-streaming-format and SPEC read 0 this way. ModelMeter returns no reading rather than a labelled zero or a saturated product, and wai-quantum-serve's POST /verify renders an unmetered receipt as "joules_micro": null, "metered": false.
  • The labelled JWP leaf. An object's class rides in its Merkle leaf, BLAKE3("moq-jwp:merkle-leaf\x02" ‖ content_hash ‖ u64_be(joules_micro) ‖ class ‖ u32_be(len(origin)) ‖ origin), so the group signature covers the object's figure, its class with its evidence, and its origin (jwp-receipts §2.2, §2.4.2). A label, or the origin, can be neither added, stripped nor altered in transit.
  • The labelled group payload. moq-jwp:group-sign\x02 signs the legacy fields and then the total's class, its coverage and the parent link (§2.3, §2.4.1); the legacy payload never signed the parent. The class is composed from the objects' classes by an integer rule — the weakest class, and for a hardware class ⌈Σ F·p / Σ F⌉ ppm over the longest window — and a verifier holding every object checks it (§3, The group total), alongside the sum.
  • Position. An object must sit at the position its inclusion proof walks (§3 step 5, proof_position_mismatch), which closes the duplicated last leaf of an odd level and an object presented at another object's index — the latter could hide a weaker class from the group total. Group signatures are held to the strict Ed25519 rule claims use. §3.3 lists the delivery reasons; a group's link hash is defined over its signing payload and signature.
  • WAI_RECEIPT (extensions/moq-streaming-format.md §5): 0 is the unmetered marker, group_receipt is the group's link hash, and the property's class reads as signed only where it equals the object's leaf label or an accepted energy claim; otherwise it is discarded (property_disagrees) or read as unlabelled. A class read as signed names the key that signed it — the group's for a leaf, the claim's for a claim — so a class a relay signs into the property under its own key is never read as the publisher's.
  • What stays unverifiable. energy-measurement §5 and energy-binding §3 now say it is the measurement — the joules and every field describing how they were obtained — not one number, and that a report's values can be checked for consistency, which is not truth. energy-binding §7.4 no longer says green metadata is registered; it is not yet.
  • Implementation. wai-rs (feature receipt_claims): jwp_delivery reads and verifies both forms of each JWP receipt, the group total and the property; energy_figure holds the profile registry and views every figure-bearing receipt family, the quantum receipts through MaybeLabelled; the reference meters write a measurement report beside the receipt by default (--no-report to opt out; --previous-report with no file is a usage error), and wai_prov presents its total as Partial over the measured steps, or UNMETERED, never as 0 µJ. joule-code's joule-moq (0.2.0) builds and verifies both labelled forms, checks position, holds publisher keys and group signatures to the strict rule (a non-canonical key encoding is refused as a key), refuses an unsigned coverage on a legacy group, and runs every leaf, group and object vector of the new jwp-conformance/ corpus.
  • Conformance and CI. claim-conformance/ gains vectors for every new reason, the arithmetic at its boundaries, liveness faults (a held value among them) and the labelled leaf; the earlier vectors are unchanged and pinned. jwp-conformance/ holds the JWP corpus (wai_jwp_vectors) and jwp_verify.py, an independent verifier. CI now runs claim_verify.py, differential.py (fixed seed) and jwp_verify.py on the linux runner, and the JWP corpus on every platform of the conformance matrix. The conformance build includes receipt_claims, so wai_meter and wai_world_meter are built on every platform of the conformance matrix; wai-energy-receipts.yml builds those two, wai_video_meter and wai_quantum_meter on linux, linux-arm64 and macos; no workflow builds wai_prov, which needs codecs. joule-code's CI runs joule-moq with its QUIC transport.
  • Build. Every reference meter bin (wai_meter, wai_world_meter, wai_video_meter, wai_quantum_meter, wai_prov) now also requires the receipt_claims feature, because it writes a measurement report. Cargo skips a bin whose required features are off without an error, so a build that names only the older features (--features video_receipt, say) no longer produces that meter; add receipt_claims.

Wire. Every receipt issued in a legacy form keeps its bytes and verifies as before: v1 groups, \x01 leaves, every claim and corpus vector already pinned, wai-quantum's receipts (no wai-quantum code changes). The new forms are refused by software that predates them: a verifier or relay refuses a labelled group at §3 step 2 and a labelled object at §3 step 5, and a claim verifier refuses measurement and operating-point as unknown_class. A group's sign_version other than 2 is group_label_invalid. External JWP implementations must adopt the \x02 leaf and the labelled group payload before verifying objects or groups that use them, and hold publisher keys to the canonical-encoding rule; the in-tree joule-moq does, and its public API changes are listed in joule-code's CHANGELOG. wai-quantum-serve (0.4.0) changes the POST /verify response: an unmetered receipt renders as "joules_micro": null, "metered": false where it rendered its marker as the integer 0, and every other receipt adds "metered": true; a client that reads joules_micro as an integer must accept null. New meter receipts can declare one ppm more than before, because they seal from the published integers, and a meter now refuses a rail that is not live where it used to seal a figure.

Key discovery, signing authority and module revocation

  • extensions/jwp-receipts.md §7: a signer can say where its keys are published, and a verifier can confirm it. A signer_id of the form did:web:<host>[%3A<port>]#<key-id> names a web origin, which publishes at /.well-known/wai-keys a key log: revisions signed by a root key that pre-commits to its successor, each listing the origin's keys, the authorities each holds (delivery, energy/<acquisition class>, lawful-basis/declaration or /decision, log-inclusion, soft-binding, binding-recovery, measurement, operating-point, receipt, module/decoder, module/reconstruct) and whether it is active, retired or revoked. The path is published now; its registration in the IANA Well-Known URIs registry is to be requested. A did.json on the same origin SHOULD list exactly the log's active keys, and the log governs.
  • A verifier that has anchored the origin — pinned out of band, or fetched from the origin over HTTPS and recorded as such — confirms an accepted receipt, claim or module grant against the log, offline, and reports confirmed, refused or unconfirmed with a named reason beside the item's own verdict, never folded into it. An item needs every authority the item fixes: a group receipt needs delivery and energy/<class> for each class its objects' labelled leaves and its labelled total carry, another family's receipt receipt and energy/<class> for each class its figures carry, a claim its class's authority, and an energy authority is read from any energy_provenance a claim's body carries, whatever its class — so a measurement or operating-point claim needs its class's authority and the energy authority of the figure it restates, pinned-only for a hardware class. An Ed25519 did:key names one key, and one naming a key other than the signer's is refused; a did:key longer than any Ed25519 key's spelling is a label, refused before it is decoded. The head walk ignores revisions that are not held, unattached, or signed by a key their parent does not authorise; the owner's rotation supersedes a stolen root's revisions; an older log retrieved after a newer one was anchored is reported as rolled back.
  • Pinned-only authorities: the hardware-class energy authorities and the lawful-basis authorities, and any authority registered for pre-emption, are confirmed only under a pinned anchor that has not moved (anchor_not_pinned); every confirmation reports whether its anchor was pinned or fetched. A pinned record moved forward by a later retrieval stays pinned and keeps the revision that was pinned, and a pre-emption authority is read from that revision only. Beside an item that needs a hardware-class authority, a verifier reports whether its key also holds delivery (also_delivers); the recommendation that it not is a SHOULD.
  • Revocation refuses every item under a revoked key, whatever time the item carries. A retired key's untimed items stay confirmable until the key is revoked, and are reported as untimed.
  • §7.9, decoder and reconstruct modules: a module, or a parameter set an envelope pins, is identified by the SHA-256 of its bytes and authorised by a signed module grant from a key holding module/<form>, for named capabilities and a bounded window. A grant is held only from one of the sink's module origins — an origin its relying party names, or the origin under which it confirmed the envelope's delivery — so anchoring an origin for its receipts does not make it a source of modules. The key log revokes modules by digest (revoked_modules, append-only), and a digest revoked in the head of any origin the sink has anchored is not held, whichever origin grants it. Each log declares grant_max_s and status_max_age_s under normative ceilings; a signed key status from the head's root, re-issued on each heartbeat and held only if it names the head or an ancestor of it, keeps a sink current. Eight ordered checks decide whether a sink runs a module; every failure — including no network, a stale status and no trusted clock — means the module is not held, and dispatch falls back: a sink never reports a file inert because of module authorisation while a fallback remains, and an encoder whose capability needs a module declares one. The kill latency grant_max_s + status_max_age_s is a normative parameter. The ceilings are 86400 s for grant_max_s and 3600 s for status_max_age_s.
  • Module revocation through the parameter-set pin. SPEC.md §4 folds the hook into its steps: a sink that runs a module obtained apart from an envelope holds it under §7.9, or the capability is not advertised for that envelope. SPEC §3.1's check 5, reserved until now, runs at a sink that holds a key log, keyed on the pin's form and sha256, before check 6. It has two parts. Revocation runs for every capability a pin applies to and needs no grant, status or clock: a set whose digest the head the sink reaches from any anchor it holds revokes is refused with prior_revoked, whichever check a grant would fail first. Authorisation runs where the relying party holds the capability's pinned sets to §7.9, its choice per capability: §7.9 authorises the set as a decoder module, and any other failure, offline and no grant included, is prior_not_authorised. Dispatch moves to the fallback as for any refused pin. An envelope that pins no set does not escape check 5: the set an unpinned decode reads is checked under each digest a pin over it could name. A sink configured with no key log does not run check 5. key_log::module_check implements §7.9's checks; key_log::SinkKeyLog runs check 5 in the reference dispatcher, given to pin::verify_with or to neural::ModelRegistry::with_key_log, which also runs it before an unpinned decode. Library tests in CI dispatch a revoked set to its fallback, report prior_revoked when none remains, refuse a revoked set whose grant fails an earlier check, whose sink holds no grant, or whose capability is not held to §7.9, and fall back when the sink is offline; the neural sink gates run the same refusals through neural::decode_envelope over the committed int_mlicpp set, pinned and unpinned. A key log that names the subject's origin holds for one envelope, so a registry given one decodes that envelope only. The reference dispatcher runs no module obtained apart from an envelope; wai-web and the C ABI take no key log. neural::ModelRegistry gains a private field, so a struct literal no longer builds one; build it with ModelRegistry::new() and its builder methods.
  • wai-rs: signer_id (ungated, no dependency) reads a signer_id; key_log (feature key_discovery, no new dependency, checked to build for wasm32) implements §7. Outside the crate, an item to confirm is read from bytes that verified: Item::from_claim from an accepted claim, Item::from_group from a group receipt and the objects included under it, Item::from_family from another family's receipt through its figure view, given the signer_id and signed time the same receipt carries. wai-keys builds an origin's key log from seeds the operator names, appends retirements, revocations, revoked modules and rotations, signs statuses and module grants, verifies a log as §7.3 anchors one — afresh, or as a later retrieval against a fetched or pinned anchor already held — and emits and checks a did.json. It creates no key material, never prints a seed, and refuses a published test key unless told the log is a test log.
  • signer-conformance/: a corpus of anchoring, confirmation and module vectors with declared verdicts, generated and verified by wai_signer_vectors in CI on every platform of the conformance matrix, with a canonical fixture computed by a separate implementation (signer_verify.py), which also reproduces every vector's verdict. CI runs it on the linux runner and requires its verdicts to equal wai_signer_vectors' line for line (jwp-receipts §5.8).
  • No wire format or committed signed vector changes. Every receipt and claim verifies as before, and every vector of the claim corpus, those of the measurement and operating-point classes included, keeps its outcome. The reference meters, whose seeds are published, now write a label as signer_id rather than a real origin, so their signed output differs from before; the world demo's signer is a label too. A CI test holds every did:web literal in a tracked .rs, .json or .js file under wai/ (signer-conformance/ aside) that names a domain, and every origin-bound one, to a reserved example name. wai-quantum-serve's /verify reports the signer's level beside validity.
  • transparency §6 pointed to "jwp-receipts §7" when no §7 existed; it now points at this section. SPEC §6.2–§6.3, confidentiality §6–§7 and jwp-receipts §6.6 no longer say that a relay signs, or that checking a receipt needs no key: it needs the signer's verifying key, which the receipt carries. prior-carriage §6 no longer says WAI has no key discovery.
  • Not done here: a key log for wai-web and the C ABI; an independent time that could reinstate an item signed before its key was compromised; sub-delegation between module grants; an authority for pre-emption, relay attestation or reconciliation, which have no class; checking a log's evidence under its profile; the keys an OER receipt is checked with.

Typed receipt classes, and a second (soft) binding class

  • extensions/jwp-receipts.md §2.4.3–§2.4.7: every receipt now belongs to one class. group/object are the delivery class, with their wire form unchanged; a new claim kind carries the others on one envelope (JCS, RFC 8785), one Ed25519 signing rule and a per-signer chain kept separate from the group chain. Classes: energy, lawful-basis (a declaration of purposes each with its own basis, or a decision recorded against one) and log-inclusion (the log's evidence carried verbatim — accepting the claim is not verifying the inclusion). An unknown class is rejected, never skipped; delivery is reserved to group/object.
  • energy gives an object's figure a signed place for its acquisition class, which the object receipt has no member for (extensions/energy-measurement.md §1 now says so, and that a group total still has none). The claim restates the figure and carries the class with its evidence in energy-measurement §2.7's JSON fields — energy_provenance, energy_uncertainty, energy_counter_filtering, energy_calibration_ref, present and null where a class has none — admitted through crate::energy_class: a refined hardware class needs its declared uncertainty above the resolution floor, a counter its filtering state and an instrument its calibration reference, and legacy HwShunt is refused (energy_legacy_acquisition, energy_acquisition_evidence). Its grade may not rank above what the class supports under extensions/oer-receipts.md §2 — both refined hardware classes measured, ModelBased derived, Estimator projected — so a verifier that checks an accepted claim against the OER receipt it names holds one graded within the class the claim states. A named OER receipt is read, verified and its figure read by the OER module under oer-receipts' own rules (a vector document read under §4's profile, a figure read as §4 reads a quantity and compared exactly); the claim layer has no reading of OER of its own.
  • §2.6 and §3.2: a second binding class. A publisher-signed soft-binding declaration names a registry algorithm (reverse-DNS form, aligned with the C2PA soft-binding algorithm list), its type, the value and an https resolution endpoint; a binding-recovery record states attests: "ancestor". The ancestor rule: a receipt reached through a soft binding attests the ancestor, never the object in hand, and the transformation's energy is unaccounted rather than zero. A declaration counts only if signed by the publisher of an already-verified delivery receipt for its subject, and only once its own signature is verified — when a recovery is performed, and when a recorded one, or a lawful-basis decision, is checked against the declaration it names (context_invalid otherwise).
  • §3.1 step 3 restates RFC 8032's own decoding rule (a key has one canonical encoding) and range rule (S < L), and fixes the two choices on which RFC 8032 verifiers differ: keys and R of small order are refused, and the equation is the cofactorless one. Step 1 states what JSON is malformed where parsers part — a byte-order mark, a repeated member name, an escape that leaves a surrogate unpaired, nesting deeper than 32 — and that a number never is (1e400, -0 and a 400-digit integer are field_type); issued_at must name a day its month has; resolution_endpoint's host must be non-empty and its port digits. Step 2 checks exactly the type each member table gives, a member's type and encoding together in the table's order (a lawful-basis body's record first, a nested object's members where the table reaches it); what a class's rules add to a type — a non-zero figure or work count, a non-empty basis, an alg in registry form, an https endpoint — is step 4's, with the rule's own reason.
  • wai-rs --features receipt_claims (crate::claim_receipt, wai_claim_vectors) and claim-conformance/: vectors covering every reason, run by CI in both corpus loops, with the feature added to the library tests. A canonical fixture computed by a separate implementation (claim_verify.py: Python JSON with UTF-16 member order, cryptography Ed25519 plus step 3's key, R and S checks written from the curve's definition, BLAKE3 from its specification) is pinned in wai-rs; that script also reproduces every vector's outcome, reading OER receipts with the OER reference verifier. claim-conformance/differential.py runs both verifiers on generated inputs the corpus does not hold and fails on any differing verdict. Both are run by hand, not in CI.
  • OER: py_float_repr wrote a double half-way between two shortest decimals with the upper one (75.036163330078125 as 75.03616333007813), where the reference's repr writes the even one (…812). §4's number profile keeps floats out of every receipt body, so no content address or verdict depended on it. str() of a number no check reads did — a vector's name, which the reference and wai-rs (wai_oer_vectors, wai_oer_diff) print — so the two printed different names for such a vector. The function now takes the length from the shortest round-trip formatter and the digits from the exact one, keeping the shortest digits where the nearest decimal of that length does not read back (next to a power of two); a table CPython generated pins fourteen such doubles.
  • Not done here: key discovery (every claim signature presumes the verifier holds the key), an authority class for who may sign which class (a lawful-basis decision is checked against its declaration whoever signed it: nothing supplies an operator's key the way a verified delivery receipt supplies a publisher's), region- or time-scoped soft bindings, and checking a log's receipt under its profile.

wai.neural.int_mlicpp — the MLIC++ entropy topology at decode-equivalence

  • New capability wai.neural.int_mlicpp (wai-rs/src/int_mlicpp.rs, behind neural_int): the channel-autoregressive × checkerboard entropy model of wai.neural.mlicpp — K channel slices, each decoded as an anchor pass then a non-anchor pass, an entropy network over concat[latent decoded so far, hyper] re-run before every pass — with the hyper-decoder, the entropy network and the synthesis all integer networks (int_hyper_synth), the scale → distribution step int_entropy::det_build_index, and the integer rANS. Registry: NeuralIntegerExact (decode-equivalence), Lossy, royalty-free (its decoder is this crate's; the patent status of the techniques it implements was not examined).
  • Why: the float capability's validation run decoded to garbage while its encoder evaluated the entropy network at its runtime's default thread count and the decoder single-threaded — one scale fell on the other side of a table edge at symbol 416 of 12,288, and every later symbol decoded wrong. Matching the execution settings fixes that run; it remains a convention. Here every scale is an integer both sides compute exactly, so no thread count, runtime or platform can move a bucket.
  • Wire: a WIM1 payload (36-byte header, then the hyper-latent and latent rANS streams) and a one-file WMB1 model bundle (three WHM1 networks and the entropy tables), so a pinned prior's SHA-256 covers everything the reconstruction depends on. The neural dispatch decodes it (decode_envelope, decode_envelope_strict) and checks a pinned prior against the very bytes it decodes with.
  • Totality: every value the stream controls is range-checked. Before the decode allocates anything, each network's shapes are derived from its layer geometry (the new int_hyper_synth::net_shape, which shares its geometry with the checked run): a header whose shapes the model does not produce, or on which any network would form a buffer — an activation, a transposed convolution's uncropped plane — over Header::MAX_TENSOR elements, is refused as a model that does not fit the payload. tests/int_mlicpp_preflight.rs counts allocations: refusing such a header allocates a few kilobytes at most, where the decode without the check allocated the tensors the header asked for, and on a synthesis wider than the cap aborted the process on a 3.4-petabyte allocation. Latents go through the new int_entropy::dequantize (sym · 2^frac + base, None where i64 cannot hold it; Kani harness kani_dequantize_is_exact_or_refused), and every network through the new int_hyper_synth::run_net_checked, which runs run_net layer by layer only while no product, partial sum or requantised value can leave its type and refuses otherwise. A test moves one channel's scales across table edges: the stream leaves the encoder at the first moved bucket, and the decode is refused when the desynchronised latents would overflow the entropy network. With the accumulation check removed, a damaged copy of the committed stream panics the overflow-checked build.
  • Conformance vector wai-rs/tests/vectors/int_mlicpp/ (from tools/wai_int_mlicpp_export.py, deterministic in its seed): synthetic weights, quantised like a trained model's; no trained MLIC++ model is used, so it shows decode-equivalence and nothing about compression quality. A 64×64 image, 8 latent channels in 4 slices, all 16 scale buckets used, 47 of 512 residuals and 5 of 16 hyper-latent symbols coded by the rANS escape. The export computes its reference in numpy integer arithmetic under explicit int64 range checks and decodes its own rANS bytes back, pass by pass, before writing. Pinned: the RGB (FNV-1a and SHA-256) and FNV-1a over the bucket and symbol sequences.
  • Evidence: decoded to the pinned bytes on macOS arm64 and on wasm32-wasip2 under wasmtime; x86_64 not measured locally (determinism-tiers §6). A golden in byte-exact-conformance puts it on the four-platform matrix; tests/int_mlicpp.rs runs on every wai-conformance leg in both profiles, and the dispatch test in wai-neural-gates.
  • Registry tests now pin a recorded basis for every NeuralIntegerExact capability (NEURAL_INTEGER_EXACT_EVIDENCE), as STANDARD_DEFINED_EVIDENCE does for StandardDefined.
  • int_hyper_synth::IntModel::from_bytes, the compact network form the integer neural decoders load, is total on damaged bytes. It reserved room for the layer count it read before checking that the bytes held that many layers — about 480 GB for a count of 2^32 − 1, which aborts wherever that allocation fails — and formed layer sizes and read offsets in unchecked arithmetic, which overflows a 32-bit usize (on wasm32 the first case panicked with a capacity overflow). Both are checked now, and a valid model parses to the same layers.
  • int_hyper_synth::run_net now applies each layer through one function that run_net_checked shares, run_net_checked takes each layer's geometry from the function net_shape uses, and decode's pixel quantisation is to_rgb8; all refactors, and every committed vector reproduces its hash.

Energy acquisition — the receipts wai-rs and wai-quantum seal can carry their class; meter windows open from rest

  • [extensions/energy-measurement.md] §2.8: the world-session, film and reel, haptic, score, 4D-splat, audio-scene, ternary-reconstruction and provenance-step receipts, and every wai-quantum receipt, carry the acquisition class as an optional signed label. Absent, a receipt's signed bytes, identity hash and JSON are exactly the legacy ones, and the figure is unlabelled: accepted as legacy, never read as any class. Present, the signing domain's version byte becomes 0x02 and the class's §2.7 encoding follows the figure it labels, inside the signature. A label is refused as legacy HwShunt, beside a zero figure, or as a refined class without its evidence. Provenance labels each step, and a quantum bill of materials each entry.
  • Wire: unlabelled receipts are unchanged; tests pin one receipt of every family to the bytes it had before the label existed, and every committed conformance corpus still verifies. JSON evidence fields are now energy_-prefixed (energy_counter_filtering, energy_calibration_ref) in every snake_case carrier, including the rendered wai.video.int_motion receipt: a quantum job receipt already renders calibration_ref for the calibration receipt it ran under. §2.7 now fixes every field name.
  • §2.9: IOReport is classed OnChipCounter — the silicon's own reading, whose undisclosed derivation is part of the systematic error an OnChipCounter declaration already excludes. The section lists the documents elsewhere in the repository that class IOReport as ModelBased or Estimator, which this revision does not change.
  • §2.3 and §3: the completion-count error depends on how the burst window opens. The reference meters read the count the moment their workers started, with no barrier, so the count read low by about N/2 and every per-unit figure read high by about N/(2C) — the symmetric model did not describe it. Workers are now held at a start barrier and released as the window opens: the work lies in [C, C + N), the figure divides by C + N/2, and the declared term is N / (√3 · (2C + N)). The window-opening mode is part of what a published figure reports.
  • §2.3: a declaration describes the figure as sealed, and joules_micro is a whole number of microjoules, so the declaration includes the figure's own resolution: 0.5/√3 µJ for an estimate rounded to the nearest microjoule, 1/√6 µJ for the difference of two whole-microjoule register readings. The measurement's own terms are carried across in microjoules and the sum is divided by the figure sealed. A 0.563 µJ estimate measured to 1.54 % is sealed as 1 and declared 28.9 %. Every verifier refuses a hardware class with joules_micro × relative_ppm < 288 676, a declaration smaller than the integer's own rounding. The receipts built on energy_class, every optional-label family, the wai-quantum labels and the C2PA energy binding (bound or parsed) apply that check.
  • §2.8: quantum_energy::MaybeLabelled parses and verifies a quantum receipt in whichever form it arrives. It takes the form from the JSON and refuses a malformed label rather than reading the receipt as unlabelled. The wai-quantum verify CLI and the HTTP handler's POST /verify use it, so the labelled receipt wai_quantum_meter emits for a measured figure verifies on both, and both restate the class. MaybeLabelled::verify_against_calibration checks a job's link to a calibration in either form, by the identity of the form the calibration is in; the wai-web job demo accepts a labelled calibration the same way.
  • §2.2: the OCMF example now names what the regime rests on (a conformity-assessed, legally verified measuring system, not a calibration certificate); OnChipCounter is defined to cover any instrument whose uncertainty is not traceable, and the class is stated not to record where the sensor sits. §2.1 names the GREEN draft and its revision. §2 places the three-name mapping rule on producers. [extensions/energy-binding.md] and [extensions/moq-streaming-format.md] say "refined hardware class" where they meant it, so legacy HwShunt is not read as missing its evidence.
  • Reference implementation: energy_class gains the label rules (labelled_domain, write_label, check_label, the JSON helpers) and the window-opening model (CompletionCount, WindowOpening). Each wai-rs family gains an energy_provenance field and with_energy_class(), which re-signs with the receipt's own key; ProvStep gains the same. wai-quantum adds quantum_energy without changing any existing type: Labelled<R> and LabelledQbom re-sign a sealed receipt over the labelled payload, and the inner receipt's own verify() refuses the result, so a reader unaware of labels fails closed. The new energy_burst module runs §3 once for every meter; wai_meter, wai_world_meter, wai_quantum_meter, wai_prov and wai_video_meter now seal OnChipCounter with a declared uncertainty only when §4 and §2.3 allow, and seal unmetered otherwise. The world and quantum meters no longer count in batches, and the reported standard deviations are the sample ones the declared uncertainty uses. Each meter prints the resolution term and, when it seals unmetered, why.
  • Rust API (wai): the receipt structs of the families above gain a public field, and per_unit_relative_uncertainty takes a CompletionCount. wai_meter, wai_world_meter and wai_prov now emit an unmetered receipt instead of exiting when no power reader is present.
  • CI: a new wai-energy-receipts.yml runs the video/energy features, the label and pin tests of every family, wai-quantum's own tests and the HTTP handler's verify test, on x86_64 and arm64. It builds four of the five reference meters (wai_video_meter, wai_meter, wai_world_meter, wai_quantum_meter). It does not build wai_prov, which needs the codecs feature (system libjxl); no workflow enables that feature. wai-conformance.yml now also triggers on wai-quantum changes, which its quantum corpora depend on.
  • §2 and §2.8 now say what is enforced. The verifier rules are: a label is checked wherever one is present, and a receipt defined with a class from the start is refused when it carries a non-zero figure without one. Labelling a new figure in an optional-label family is a SHOULD, where it was a MUST that nothing checked and the reference implementation did not meet. An unlabelled figure sealed today has the bytes of an issued one, so no verifier can tell them apart. The reference implementation does not hold its producers to the rule either: in these families no constructor takes the class, and a label is added afterwards, by re-signing, or for a provenance step before its receipt is built. §2.8 says how a library could hold its callers to it: a labelled constructor, with the unlabelled path kept only for recreating issued receipts and deprecated, as HwShunt is. The reference implementation has not done that. The reference meters label every non-zero figure they seal. §2.8 names the two producers that do not: world_model::frame_receipt, which is not told its caller's classes, and the wai-web demos, which seal illustrative figures. It also says why a corpus generator that recreates a committed corpus, such as wai_quantum_ops_vectors gen, is not counted: every committed corpus predates the label.
  • [extensions/energy-measurement.md] §1 states its scope: every receipt profile WAI defines that carries an energy figure, a list that is not closed. An OER/2 receipt is outside it. Its members are fixed, it has no member for the class, and its evidential axis is the grade. No rule of the extension binds an OER receipt or its producers and verifiers, so an OER receipt with a non-zero figure and no class breaks none, and the §4 publication gate and §6 reporting contract do not reach it either. Only §2's rule for carrying a WAI figure into a three-name vocabulary reaches past the governed receipts. §1 also records that two receipts WAI defines fall short of §2: the JWP group receipt, which signs a non-zero joules_total, and the JWP object receipt, which carries joules_micro. Neither has a member for the class. Where a WAI_RECEIPT property accompanies an object over MoQ, the object's class travels in it, but no signature covers that property, so it falls short too. Nothing carries a class for a group total, and §2.8's rule that a total carries no class of its own does not cover it, because a group's parts are labelled outside the group receipt, if at all. Giving these a signed place for the class remains open. [extensions/jwp-receipts.md] §2.4.1 points to this from joules_total. [extensions/interactive-worlds.md] §8 now names the session receipt's own signing domain, wai:world-receipt\x01, which the reference engine already used.
  • §2.6 says that the wai.video.int_motion and wai.video.* OTT constructors accept HwShunt, so that issued receipts can be recreated, and that nothing in them refuses it. The variant's deprecation warning reaches only code that names it; a class parsed from its label can be HwShunt without one. No producer in the reference implementation emits it.
  • The resolution floor is stated as enforced: a declaration must reach the 0.5/√3 µJ (≈ 0.289 µJ) that rounding the figure to whole microjoules adds, joules_micro × relative_ppm ≥ 288 676. SPEC.md, the wai-quantum README, both crates' BelowResolution messages and several doc comments called it a 1 µJ floor, which is stricter than the rule verified. [extensions/energy-binding.md] §4 now states the floor, which bind(), from_json() and C2PA recover() already applied. The energy_class and quantum_energy docs say what the floor is: the rounding term every whole-microjoule figure carries. A figure read as a register delta carries the larger 1/√6 µJ, which its producer must declare and no verifier checks.
  • [extensions/oer-receipts.md] §2 maps the evidential grade onto the refined classes: CalibratedInstrument and OnChipCounter support measured, and so does legacy HwShunt, for a figure sealed before the refinement or a refined figure mapped into the three-name vocabulary. Neither an OER/2 receipt's provenance.device nor its energy.method names a class. The OER wire format, vectors and verifiers are unchanged.
  • Not yet covered: the in-browser demos of wai-web that seal into an optional-label family seal illustrative figures, fixed in code or passed in by the page, without a label, and the OTT manifest demo labels a page-supplied figure ModelBased. The honest class of an illustrative constant is Estimator. The ModelBased and Estimator classes still carry no declared uncertainty.

Energy acquisition — on-chip counters and calibrated instruments are separate classes

  • [extensions/energy-measurement.md] §2 refines the hardware class. HwShunt covered both a counter the measured silicon keeps about itself (RAPL, NVML, SMC, IOReport) and an external meter, although only the second can carry an uncertainty traceable to a calibration. It is now two classes: OnChipCounter and CalibratedInstrument. Each must carry a declared uncertainty: a standard (k = 1) relative uncertainty in ppm plus the integration window it holds over. The counter records whether its output was filtered (On / Off / Unknown; RAPL energy filtering per INTEL-SA-00389), and the instrument records a reference to its calibration certificate. For an on-chip counter the declaration covers only what the implementation could evaluate, never the counter's own systematic error, so it is not a total-error statement.
  • §2.1 keeps the declared uncertainty and the GREEN data-source-accuracy graded bands apart. A band bounds one device reading, while a declared uncertainty is a standard uncertainty about a per-object marginal figure. A band is never declared as a figure's uncertainty, and a declared uncertainty is never used to select a band.
  • §2.3 and §3: a per-unit figure from the bracketed protocol declares the bracket's statistical term combined with a completion-count term, N / (√3 · C) for N workers and C counted completions. Up to N reconstructions are part-done at each edge of the window, and the count cannot see them. The thread count is now part of what a published figure must report.
  • Wire: new signing tags 4 (OnChipCounter) and 5 (CalibratedInstrument), each followed by its evidence (§2.7). Tags 0–3 are unchanged and followed by nothing, so every receipt sealed with HwShunt, ModelBased or Estimator keeps its exact signed bytes and verifies as before. Tests pin those bytes. HwShunt is accepted when read, means "hardware, sub-class unspecified, no declared uncertainty", ranks below both refinements, and must not be emitted. JSON carriers (the C2PA companion assertion, the rendered int_motion receipt) add evidence fields only for the refined classes, so legacy bodies are byte-identical.
  • Reference implementation: the new energy_class module is the one definition shared by energy_binding, c2pa_emit, video_receipt and the wai.video.* receipts. verify() and C2PA recover() reject a hardware class that lacks its uncertainty. CounterMeter emits OnChipCounter with the window it measured, declaring its configured relative uncertainty combined with the register delta's resolution term, 1/√6 µJ, so a short span declares more than the configured figure. wai_video_meter declares the bracket's statistical (Type A) term combined with the completion-count term, and emits an unmetered receipt when no decode completed in the window, where it used to count one.
  • The receipt families that sealed joules_micro before the class existed now carry it as an optional label; see the next section.

Integer decoders — fast kernels that keep every output integer

  • wai-rs adds int_kernels: register-blocked convolution, transposed convolution and IGDN kernels (NEON widening multiply-accumulate on aarch64, portable Rust elsewhere), scoped worker threads (none on wasm), and a run-time Policy that selects them or the retained scalar reference kernels. The int_synth, int_hyper, int_audio, int_splat, int_world and JPEG-AI synthesis decoders dispatch to them. No bitstream, conformance vector or hash changes.
  • The claim, precisely: for every input the reference decodes, every policy returns the same integer for every output element; with overflow checks on, a fast policy panics on the inputs where the reference panics, though the panic message can differ. Geometry the fast path does not handle, including a transposed convolution whose sizes the reference's own arithmetic cannot form, goes to the reference. The fast path's own allocations and loops stay in proportion to what the reference reads and writes, whatever the stride or padding. The two paths still allocate different buffers, so an allocation that fails is outside the claim. It is argued by construction and tested, not machine-proved. The rules, the tests, their limits and the measured throughput (one machine) are in research/decode-throughput.md.
  • CI: wai-conformance runs the kernel-equivalence tests, every committed vector that reaches the kernels and edge geometry through the public decoders (strides up to usize::MAX, padding far beyond the kernel, and strides and paddings at which the reference's size arithmetic overflows), under every policy, with overflow checks on and off, on each platform leg. Geometry on either side of the padded-input bound is pinned too: at the bound the fast kernel runs, beyond it the reference does, and a test-build count of fast-kernel entries shows which one ran. wai-byte-equality and wai-formal-verification fire on changes to int_kernels.rs.

Headline decode tests fail on a missing input instead of passing

  • The int_hyper tests over the pretrained mbt2018_mean (int_hyper_synth, int_video_sota_keyframe) and the JPEG-AI whole-decoder tests (jpegai_e2e_full, jpegai_reconstruction_binding, jpegai_live) read inputs from outside the tree, and used to return — reported as a pass — when those were absent. With WAI_REQUIRE_SOTA_VECTORS=1 a missing input is now a failure that names the tool producing it; without it the skip is written to the process's real stderr, so it shows in an ordinary cargo test run. Either way, a corrupt input file fails, and so does a variable that names inputs which are not all there: a path that does not exist (WAI_JPEGAI_CODESTREAM/_WEIGHTS_DIR/_NORM, even when another of the three is unset), or a directory that does not exist or lacks a file the test reads (WAI_SOTA_INT_DIR, WAI_JPEGAI_ASSETS, the JPEG-AI weights directory). So without the switch a test skips only when no variable names what is missing: one of the three path variables is unset, or WAI_SOTA_INT_DIR or WAI_JPEGAI_ASSETS is unset or empty and its default directory (/tmp/sota_int, ../jpegai-vm-assets) is absent or incomplete. The CI jobs always set the switch, so the new test require_vectors_rule checks this rule without it: it runs the helpers in child processes with the switch and the variables set per case, and wai-sota-vectors.yml runs it.
  • New CI job wai-sota-vectors.yml regenerates the mbt2018_mean integer vectors from pinned inputs (tools/wai_sota_vectors.sh, the Python stack in tools/requirements-sota-int.txt, a public-domain photograph pinned by SHA-256, the published checkpoint pinned by its full SHA-256) and runs those tests with the switch set. It is triggered by changes to the decode path and by a weekly schedule. Besides requiring each test by name, it fails if the test log contains a skip banner, since a skipped test also reports ok. The export tool checks the checkpoint's bytes before loading them (as tensors only), because the model zoo's own download path checks no hash. The job checks the Rust decode against the Python integer reference on one runner; cross-architecture byte-equality remains the committed tiny vector's job in wai-byte-equality.yml. The regenerated vectors are not portable, and part of them is not repeatable run to run on every machine. Between an arm64 macOS machine [name removed 2026-09-30] and the x86_64 runner, the synthesis-only test's input latent, which the float model computes, differed, and with it its expected rgb_hash; each side wrote the same latent in all of its runs. Two runs on the one runner wrote different cond.json and int_synth_sota.json, most likely in the float decode's PSNR, which only the quality test reads, against thresholds. The PNG bytes of the source also differed between the Mac and the runner; the PNG encoder writes them from the pinned pixels before any model runs, and the pixels matched. So no expected hash of the regenerated vectors is pinned in the repository (the committed tiny vector's is, in tests/vectors/int_hyper/tiny.json); each run checks the hashes its own export wrote, and a hash from one run's vectors must not be pinned for another run, on the same machine or another. The pins that hold across machines are on the inputs: the photograph's pixels and the checkpoint's bytes.
  • New test integer_decode_keeps_the_float_codec_quality: the Rust decoder measures its own reconstruction against the source pixels and must stay within 1 dB of the same model's float decode on the same image. On the pinned photograph, measured on an arm64 macOS machine [name removed 2026-09-30]: integer decode 35.985 dB (8-bit PSNR of the Rust decode), float decode 36.175 dB (the export tool's PSNR of the float reconstruction), a cost of 0.19 dB, from 6328 bytes of rANS payload for 256×256 pixels. This is a relative bar on one image: nothing here compares mbt2018_mean with another codec, and quality is image-dependent — the 42.6 dB figure quoted for this codec was measured on a different photograph, which is not in the repository.
  • New CI job wai-jpegai-e2e.yml runs jpegai_e2e_full and jpegai_reconstruction_binding with the switch set, with the same by-name and skip-banner checks. Their inputs — the decoder weights, normative tables and test codestream, extracted from the JPEG-AI reference-software assets (which are not in this repository) — are fetched from WAI's versioned models root (jpegai/v1/) by tools/wai_jpegai_fetch_e2e_inputs.sh and checked against the SHA-256 manifest tools/jpegai-e2e-v1.sha256. The job also pins the SHA-256 of the decoded pixels (WAI_JPEGAI_EXPECT_SHA256): measured natively on an arm64 macOS machine [name removed 2026-09-30] and reproduced by an x86_64 Linux build under QEMU user-mode emulation, and required again on the x86_64 runner. That extends a measurement, not a registry guarantee: the registry classes wai.image.jpegai as NeuralEntropyExact, a conservative class that guarantees only the entropy decode to be byte-identical on every machine, while the exact decoder's pinned output for these inputs was measured identical on four platforms (determinism-tiers §6).
  • jpegai_live (the ONNX-runtime wai.image.jpegai decode) follows the same missing-input rule, but no CI job runs it: its model bundle exists only in the local JPEG-AI asset directory, and it needs the neural and codecs features.
  • determinism-tiers §5–§6 record the two jobs. The full-scale int_hyper row and the JPEG AI whole-decode row (split from the index-derivation row, whose test CI still does not run) now name their workflow, state that a missing input fails there instead of skipping, and state that neither workflow has run through the forge: each passed under forgejo-runner exec on the Linux runner's host in an earlier form, and its current steps have run on macOS arm64 only. §5 quotes the int_hyper cost measured on the pinned photograph in place of the 42.6 dB figure, and names the bar each job re-checks.

Prior carriage and derived-track operations — registered with their limits

  • New [extensions/prior-carriage.md]: a Prior MAY declare the wire form of the artifact its digest covers (prior.form, additive on the wire; manifests without it round-trip byte for byte). When declared, the digest covers the coded bitstream, and a sink that cannot read the form refuses the prior rather than substitute another packaging. wai.prior.nnc (ISO/IEC 15938-17:2024) is the registered form. It is optional, not recommended: royalty status is not established, and whether conforming decoders reconstruct identical tensors is not established, so a replicate cannot rest on a prior read out of a form not listed as exact (none is). As a payload, NNC takes the replicate class of a standard whose decoder conformance the registry has not checked, as MPEG-I Haptics does; its own conformance clauses were not read. wai.tensor.nnc is registered as a candidate for general tensors; its coding is in an unpublished amendment, which is tracked, not pinned. The extension also fixes the precedence of in-band data-unit signatures: WAI's digest covers every byte, and an in-band verdict is recorded beside it, never instead of it.
  • New [extensions/derived-tracks.md]: the ten derivation operations of ISO/IEC 23001-16:2021 are registered as wai.derive.* strings, each with a per-operation determinism tier. Dissolve and Scaling are untiered. No operation is exact unconditionally, Identity included. An executed instance is exact only when no sample value is converted into another representation or computed, its output does not change over the span the claim covers, and its operation's own conditions hold; the sink lists each condition it checked. A derived sample holds the tier of its weakest step. A claim over it names the operations actually executed, the parameter values each applied, the span it covers and its inputs, visual and parameter. These are not payload capabilities, and WAI does not composite. Clause 8 of the standard, which defines each operation, was not read. Every tier rests on conditions checked on the executed instance; the geometric conditions listed per operation come from the operation's title, as does whether it reads a visual input, and reading clause 8 may show a list to be incomplete. Clause 4 crops or fills an input of another size implicitly for every operation, and no list carries a condition on where such an input is placed.
  • wai-rs: codecs::derivation (registry, Condition, derived_sample_tier), codecs::DeterminismTier (the two tiers as values, re-exported by codecs::derivation), codecs::prior_form, Prior::{new, with_form, form, check_form, form_preserves_exactness}, and container::replicate_prior_refusal, which states why a replicate is refused, including over a declared form.
  • Behaviour change: Prior::is_cross_hardware_exact now also requires the declared form, if any, to be established exact (form_preserves_exactness), so a prior that declares a form is not cross-hardware exact whatever its contract says; determinism_class still reads the contract alone. A prior without a form, which is every prior written before form existed, gets the same answer as before. The rule sits in that predicate because every replicate check reads it, container::replicate_is_sound and Wai::replicate_is_sound included.
  • Each ExecutedOp names the visual inputs its instance read (reads, as an entry of inputs or the output of an earlier executed operation), because clause 4 of ISO/IEC 23001-16 lets an operation that is not first read the output of any previous operation, only new inputs, or both, so its position does not say what it read. An instance of an operation that reads a visual input and names nothing gives no tier, wherever it sits: it read an input that nothing names, whether a track, item, track group or preceding output the file lists or, where nothing lists one, the default fill picture (clause 5.1). When at least one operation executed, a call that contradicts itself (a reference to an unlisted input or to an operation that does not precede the reader, or a listed input no instance names) is a DerivedSampleError; an empty executed list gives no tier before those checks. neural::ModelRegistry::resolve_checked refuses a declared form the sink cannot read, and the reference sink reads none. A new CI workflow, wai-neural-gates, is configured to run the two model-free tests of those neural-sink refusals, which no workflow ran before, in an Ubuntu 24.04 container image; it has not yet run on a runner.
  • Rust API, source-breaking. container::Prior gained a public field, form. Its fields are all public and it is not #[non_exhaustive], so Rust code outside the crate that builds a Prior with a struct literal, or destructures one without .., stops compiling. The fix is to add form: None, or to build it with the new Prior::new(id, sha256, determinism) (and .with_form(form)), which names no optional field and so is unaffected by the next optional field added. Reading fields, serde, and functional update (Prior { form, ..other }) are unaffected. SPEC §8 versions the wire format, not this API. On the wire, form is an optional key, added as prior itself was, and absent it the manifest's bytes are unchanged. SPEC §8 also says the manifest schema is frozen for all of 1.x; how an optional key is added under that freeze is not settled here, for form or for prior. For the crate, Cargo's semver rules treat a source-breaking change in a 0.x crate as needing a new minor version: the release that carries this must be 0.4.0 or later (wai-rs/Cargo.toml still says 0.3.0). #[non_exhaustive] was not added. It would break the same literals now and, in addition, rule out the form: None fix and functional update from outside the crate, which the crate's own integration tests use. It would also make Prior the only manifest struct that code outside the crate cannot build with a literal; the others are Manifest, ModelRequirement, Conditioning, ManifestV2 and RenditionMeta. Whether they should all become #[non_exhaustive], so that adding an optional key never breaks Rust callers, is a decision for all of them together.
  • Open: the operations' four-character codes are not recorded (clause 8 of ISO/IEC 23001-16 was not read); the wai CLI does not list derivation operations; there is no NNC reader.

Broadcast and streaming codecs — registered, licence-classified, never defaults

  • SPEC §5 registers the elementary video and audio bitstreams distribution chains carry: wai.video.{av2,vvc,hevc,avc,vp9,evc,evc.baseline,avs3,lcevc} and wai.audio.{mpegh,avs3,aac}. Each names one payload framing, which §8 then fixes forever: the Annex B byte stream for AVC, HEVC, VVC and LCEVC (each standard also defines a bare NAL unit stream, which is not the capability), AV2's Annex B length-delimited bitstream, IVF for VP9, the Annex B raw bitstream file storage format for EVC, MHAS for MPEG-H 3D Audio and ADTS for AAC. The AVS3 video and audio rows name each standard's elementary bitstream; those texts were not read, so whether either defines a second form is unchecked. Each also carries a licence class with its recorded basis; two are classifications rather than verified statuses (AVS3: no published fee schedule; EVC Baseline: royalty-free by design, no formal guarantee). All are sink-supplied; none is in the floor or the recommended set. No decoder and no C dependency is added.

  • AV2 is registered royalty-free but not recommended: the reference decoder exists, deployable decode is not yet confirmed. Promotion is a MINOR revision.

  • LCEVC is an enhancement layer, carried embedded in its base bitstream or as a WAI2 companion whose companion_of must name one of its base codecs (AVC, HEVC, VVC, EVC, VP9, AV1, under every capability naming those bitstreams; codecs::enhancement_bases) and resolve to exactly one selectable rendition of it; a base takes at most one enhancement layer. The base list is WAI's registration, not a restriction of ISO/IEC 23094-2, which leaves the base decoder unspecified; a later revision may add a base (a widening, §8).

  • wai.video.lcevc and the HDR metadata descriptors (wai.meta.hdr10plus, wai.meta.dovi) are companion-only, now stated normatively: never a v1.0 capability or fallback, never a v1.1 selectable rendition. pick / primary never return them; Wai::check_component_roles (v1.0) and WaiMulti::check_component_roles (v1.1) report envelopes that break the rules (RoleError, #[non_exhaustive]). A WAI1 naming one only as its fallback does not conform and never takes dispatch step 5, but still decodes via its primary capability (§4 step 3); new Wai::dispatch_capability applies §4 with that rule, and wai decode and wai inspect both use it, so they agree on such a file. wai wrap refuses to write a non-conforming envelope and, with --companion <cap> <payload>, writes the WAI2 companion form, whose intent follows the same rule as the WAI1 form's (--intent, below), checked over the base payload's capability and each companion's. HDR metadata may bind to a neural video payload, and the base check is what refuses wai wrap wai.neural.dcvc_rt … --companion wai.meta.hdr10plus … (NeuralFloat, SPEC §7) unless another intent is given. Every companion-only capability is in a standard class, so the companion check refuses nothing yet. wai unwrap --component <i> extracts a WAI2 component.

  • SPEC §7.1 states the three rules a WAI2 companion meets, and WaiMulti::check_component_roles enforces each: (1) its capability is companion-only or unregistered — a registered standalone capability marked companion is RoleError::StandaloneAsCompanion; (2) companion_of names a capability it applies to (codecs::companion_applies_to: for HDR dynamic metadata a registered capability in the video media class — new codecs::is_video_class: every wai.video.* capability that is not companion-only, the neural video codecs wai.neural.{video_bmshj2018,dcvc_rt, dcvc_fm} and the films wai.film.{linear,reel} — or an unregistered one, whose class is unknown; for LCEVC a base codec) — RoleError::IncompatibleBase; (3) that base is present, and unique for an enhancement layer. The check previously let an HDR descriptor bind to an audio rendition, LCEVC bind to any wai.video.* rendition (JPEG 2000 included), and any standalone codec ride as a companion; the reference test that carried an IAMF stream as a companion now carries ST 2094-10 dynamic metadata [name removed 2026-09-30] instead.

  • SPEC §8 now says what these rules are, so they do not read as a redefinition inside 1.x: a registration fixes a capability's payload format and its role (standalone media, or companion-only); changing either, or narrowing what a companion-only capability applies to, needs a new MAJOR. A MINOR revision may add a rule that enforces a registration, or a manifest field's definition, as it was made, if it lists every kind of envelope it makes non-conforming. The companion rules are such rules: the HDR descriptors were registered as companion descriptors to a video payload, not standalone media; wai.video.lcevc was registered companion-only with these rules; and role = "companion" was defined as naming, in companion_of, the component it binds to. SPEC §7.1 lists the envelopes they make non-conforming: an HDR descriptor as a WAI1 capability or fallback, an HDR descriptor as a WAI2 selectable rendition (which pick / primary no longer select), an HDR descriptor bound to a registered capability outside the video media class, a registered standalone capability marked companion, and a companion of any capability with no companion_of or none that names a selectable rendition in the envelope. Such envelopes still parse. The list also records that the earlier §7.1 named immersive-audio metadata as a companion use and a reference test carried wai.audio.iamf that way, which rule 1 now rejects, since wai.audio.iamf was registered as standalone media.

  • SPEC §8 also says what registering a capability does to an envelope that used its string while no revision registered it. §7.1 leaves such a use unchecked (an HDR descriptor's unregistered base is not constrained; an unregistered companion needs only a present base), so the registering revision may make the envelope non-conforming where it uses the string against the new registration: a companion whose capability is registered standalone, an HDR descriptor bound to a capability registered outside the video class, a companion-only capability carried as a payload or bound to a base it does not admit. §8 lists these kinds once for every registration; they still parse. This covers wai.video.lcevc, which no earlier revision registered.

  • WaiMulti::companions_of no longer says every companion it returns is meant to be applied: the role check accepts an unregistered companion with a present base, and SPEC §7.1 forbids a sink to apply a companion whose capability it does not recognise.

  • The broadcast-codec dispatch guidance asks for a recommended-set WAI2 rendition, not a bare v1.0 fallback: step 5 decodes the same payload with the fallback's codec, which cannot read an HEVC, VVC or AV2 bitstream.

  • codecs::capability_license is now exhaustive with no default class, so a registered capability can no longer become royalty-free by omission. New codecs::mandatory_floor() / recommended_set(); tests hold that every default is royalty-free. wai capabilities gains a role column.

  • Replicate classes: exhaustive, and the exact class needs recorded evidence. codecs::capability_replicate_class no longer has a default arm (the determinism-tiers change below still ended it in a StandardDefined fall-through). Every registered capability is listed under one class; one listed nowhere returns None, which the tests reject. StandardDefined, which promises the same bytes on every conforming sink, is admitted only on a recorded basis — a conformance clause that requires identical output, or the WAI extension whose equivalence pins the reconstruction — pinned one row per capability in the tests (standard_defined_rests_on_recorded_evidence). The broadcast codecs enter with:

    • StandardDefined: AVC, HEVC and VVC (ITU-T H.264 §C.4, H.265 and H.266 §C.5.1: every sample a conforming decoder outputs equals the specified decoding process's) and VP9 (VP9 specification v0.6 §8.1);
    • StandardTolerance: AV2 (AV2 v1.0.0 §7.1: film grain, as AV1);
    • StandardUnclassified: EVC (both entries), AVS3 video and audio, LCEVC, MPEG-H 3D Audio and AAC, whose conformance documents were not read.

    wai.audio.ivas moves from StandardUnclassified to StandardTolerance on 3GPP TS 26.252 V19.2.0 §7.2.1: implementations based on the floating-point reference are tested by non-bit-exact procedures. New codecs::is_cross_decoder_exact(cap) is true where the class's replicate_promise is ByteExact, except for a capability whose output is computed from other envelopes' decoded output (new codecs::composes_decoded_sources: wai.audio.scene). A scene's mixdown is exact given its sources' PCM, and a source decoded through wai.audio.opus may decode differently on two conforming sinks, so the capability alone does not make it portable; new codecs::is_cross_decoder_exact_with_sources(cap, sources) is true once every source's capability is known and exact, and false when it cannot tell (no source given, or a source outside wai.audio.*). The class's ByteExact promise for a scene holds given its sources' PCM, so SPEC §7 names is_cross_decoder_exact, not the promise, as the audit for the same bytes on every sink. The promise covers the output the standard or extension specifies, not a conversion a sink applies afterwards. New tools/wai_replicate_probe.sh decodes each format with every independent decoder on the host and fails if a StandardDefined capability's decoders disagree — a falsification check, not the basis of any class.

JPEG AI exact decoder: no platform math library on the path, and a cross-platform check

  • The exact decoder (jpegai_decode::decode) called the platform math library's exp in the per-channel gain scaler, on every target. Its roundings to an integer compiled, on x86_64 and wasm32 (as rustc 1.98 compiles them), to calls to the symbols round, roundf and rint, which the link resolves; an x86_64 Linux test executable inspected took them from Rust's compiler builtins, not from the C library. The decoder now takes exp and rounding from the new float_repro module, which builds them from IEEE-754 basic operations, integer conversions and bit manipulation, so their bits are fixed by the source. float_repro::exp was within 1 ULP of the host math library on every host it was compared with (determinism-tiers §6 lists them). Its test asserts ≤ 2 ULPs. The test is ignored on targets whose C library was not measured, Windows among them. It selects by target, not by host, so it also runs on glibc versions and on x86_64 hardware that were not measured. The roundings equal the standard library's. The decoded output is unchanged (RGB8 SHA-256 dbc494a5… for the jpegai/v1 inputs).
  • The exact decoder now also refuses a target that evaluates float arithmetic in x87 extended precision (32-bit x86 without SSE2), where it would not reproduce.
  • New jpegai_decode::decode_with_stages: the same decode, also reporting the float values after each stage (latents, synthesis, each post-filter), so two platforms can be compared stage by stage rather than only by their final bytes.
  • New tests/jpegai_cross_platform.rs pins SHA-256 hashes of the parsed float inputs, of every reported stage and of the RGB8 output for the jpegai/v1 inputs. All nine matched on macOS arm64 (native), wasm32-wasip2 (a WASI runtime), Linux arm64 (glibc) and Linux x86_64 (glibc, under user-mode emulation), each running this change's final source, and on all four also before the math-library calls were replaced. determinism-tiers §6 records the toolchain and the method for each. Not measured: macOS x86_64, Windows, x86_64 hardware. The test skips without its inputs; wai-jpegai-e2e.yml supplies them and requires it to pass; float_repro's own pinned-output test needs no inputs and runs in the library tests.
  • Two checks keep math-library calls off the decode path. Clippy's disallowed_methods (wai-rs/clippy.toml), denied in each module on the path, rejects a listed float method however it is spelled. tests/jpegai_decode_symbols.rs lists the symbols of an executable that links only the decoder and its weight loaders, and fails on any C math-library function the executable imports, whatever made the call, and on any it defines other than the round-to-integral functions, whose results IEEE 754 fixes (unix targets). A definition alone shows what the linker kept, not what the decode calls: GNU ld kept an unused floor in an x86_64 Linux debug build. The conformance workflow runs both on its unix legs. They replace a scan of the source text, which missed a call spelled f64::exp(x). Details and evidence: determinism-tiers §5–§6. The registry class of wai.image.jpegai is unchanged here.
  • The gain unit's log_k constant was 0.2003654931615394, 9.2 × 10⁻⁹ from (ln 54.82 − ln 0.11) / 31, which the reference computes as 0.20036548400207613. It is now that value. At β = 0, the only β the exact decoder accepts, both constants give the same scaler for every gain-table entry, so the decoded output is unchanged.
  • tools/wai_jpegai_gain_exp_margin.py now models the reference's gain scaler as the reference computes it, in float32, rounding half to even. It fails if the decoder's log_k differs from the reference's. At β = 0 every table entry is at least 77 float32 ULPs from a rounding boundary, so the two scalers agree for any float32 exp within that. The margin does not hold over every 12-bit β: at four inputs the reference's own scaler depends on the last bit of its exp (determinism-tiers §6).

OER receipts — graded, chained energy receipts with three implementations

  • New [extensions/oer-receipts.md]: ingests OER/2 draft 0.1. A receipt records one unit of work, its joule cost, the cost's evidential grade (projected, simulated, derived, measured — orthogonal to the acquisition class, mapping stated), and what it rests on; canonical, content-addressed, hash-chained and Ed25519-signed. Conformance is reason-set-equivalence plus a canonical-form fixture the vectors cannot check themselves.
  • New oer-conformance/: the upstream 9 vectors, 1 from the portable C kernel (settle/, Apache-2.0), 99 hand-declared edge vectors — every reason the upstream nine never exercise, each number shape and decimal-string defect and each limit from the accepting side, each bound, each of the four tolerances at and past its edge on both sides and past 34 digits, each step of the order of the reasons reported alone, each member judged on its value, each claim an abstention cannot make, each key a signature cannot rest on, and the extremes a document may hold — and unreadable/, the documents every verifier must refuse whole. They found five defects in the reference verifier (a mis-quoted missing_capability, three crashes on missing members, a repeated receipt id hiding reasons), and a class of inputs on which the reference and wai-rs reported different reason sets or crashed (parser-dependent numbers and versions, lenient decimals, members of the wrong type, rounding past the 28th digit, documents at the edge of what a parser can read); all fixed in both.
  • wai-rs --features oer (crate::oer, wai_oer_vectors): a third, independent verifier; CI runs it on the four-platform matrix and rebuilds the C kernel, requiring its output byte-identical to the pinned corpus.
  • oer-conformance/fuzz_differential.py with wai_oer_diff: seeded differential fuzzing of the reference verifier against wai-rs over mutated corpus documents; any divergence in reasons or refusal, or any crash, fails it.
  • Departures from draft 0.1, each in the extension's §9: decisions are named oer.decision.*, not wai.settle.* (WAI's namespace is for payload formats); the stage list behind bundle.provenance_root is serialised exactly as the body is, pinned by a non-ASCII fixture; the number profile is a MUST (I-JSON integers, decimal strings in JSON's number grammar, anything else malformed); every composition check is exact, with ln 2 pinned as a decimal, and every tolerance scales by the figure's magnitude; a document outside the document profile is refused whole, a repeated member name included; every mistyped member has one named reason; published keys are decoded strictly, so a small-order key cannot verify every message; oer is a string; and a decision that was not earned is receipted as abstain — no answer, no record — which a new reason, abstention_settled, holds to that. escalate is a verdict returned from upstream, as the corpus always had it.

Determinism tiers — naming the reproducibility spectrum for neural decode

  • New [extensions/determinism-tiers.md]: names the two guarantees a neural capability can make, so a manifest can declare which it satisfies and a verifier knows what it may check. entropy-consistency — the entropy decode reproduces across devices (integer entropy arithmetic or explicit parameter transmission); JPEG AI sets its bit-exact conformance point at this tier (the entropy-decoder output). decode-equivalence — the whole decode to the final medium reproduces on every conforming sink, so the rendered result has a portable hash; WAI's integer capabilities are registered at this tier, and it is the precondition for a hard reconstruction binding. Referenced from SPEC §7 (Replicate-soundness); framed self-referentially.

  • Normative: over a neural capability, intent = replicate promises decode-equivalence (SPEC §3, §7; standard codecs, below). The first version of the extension called entropy-consistency the floor for a sound neural replicate, and SPEC §7 said the same in its closing paragraph while requiring an integer decode path in its rule. Resolved: a neural capability backs replicate only if it is registered at decode-equivalence, which §7 admits only by the integer construction; entropy-consistency MUST NOT back it, and neither may a reconstruction that one implementation pins through fixed-order float stages. A tier is a property of the capability as registered, not of one implementation.

  • Downgrade: wai.image.jpegai is registered at entropy-consistency, not decode-equivalence, and no longer backs replicate. Its registry class moves from StandardDefined to the new NeuralEntropyExact (integer entropy decode, float later stages). Its decode has float stages and a float ONNX fallback, so decode-equivalence never held as a property of the capability, and StandardDefined ("the neural float hazard does not apply") did not describe it. What the exact decoder does is unchanged — one pinned reconstruction, reproducible across machines under the conditions in the extension's §5 — and is now stated as a property of that decoder. A reconstruction binding over wai.image.jpegai is checkable only with that decoder (reconstruction-binding §2). Earlier entries below that call the JPEG AI decode decode-equivalent, or byte-identical on every machine, are superseded by this one.

  • replicate over a standard codec promises the named standard's decoder conformance, not byte identity across sinks. The first form of this change stated in SPEC §3 that replicate promises the same bytes on every conforming sink, while every standard codec stayed StandardDefined and passed the rule — including codecs whose standard conforms decoders to a bound around a reference (RFC 6716 §6 holds an Opus decoder to opus_compare thresholds, not to identical output). SPEC §3 and §7 now state the promise per registry class, and two classes are added:

    • StandardTolerance — the named standard holds decoders to a criterion around a reference: wai.audio.opus, wai.image.jpeg, wai.image.jxl, wai.image.jp2, wai.video.j2k, wai.video.av1, wai.video.av1.lossless, wai.image.avif (AV1 §7.2: identical output unless film grain is signalled, and then no "perceptually significant differences", a criterion the AV1 specification leaves undefined), wai.audio.iamf.
    • StandardUnclassified — the named standard's conformance clauses have not been checked: wai.audio.ac4, wai.audio.ivas, wai.mesh.draco, wai.pc.gpcc, wai.pc.vpcc, wai.splat.sog, wai.haptic.mihs, wai.meta.hdr10plus, wai.meta.dovi, and the pass-through asset formats wai.mesh.glb and wai.splat.spz.

    StandardDefined keeps the capabilities whose reconstruction is specified exactly (PNG, FLAC, zstd, XZ and the WAI-native capabilities). replicate over the reclassified capabilities is still honoured; what it promises is narrower. SPEC §7 also states that a conversion a sink applies after the standard's decode (PNG depth reduction, YUV→RGB, JPEG colour space, rendering an IAMF mix) is not part of the promise, and that an extension composing other capabilities' output is only as exact as their classes (spatial-audio §4 now says so for sources decoded through Opus). The clauses read for each classification, and what was not read, are in research/media-codec-registry.md. API: new ReplicateClass::{StandardTolerance, StandardUnclassified} variants, ReplicatePromise and ReplicateClass::replicate_promise; wai capabilities prints standard-tolerance / standard-unclassified for them.

  • wai.image.jpegai stays NeuralEntropyExact as a conservative choice pending a reading of its conformance part, ISO/IEC 6048-4. An earlier form of this change said in SPEC §7 and the extension that its standard does not bound the reconstruction, so no weaker promise than decode-equivalence was available. Part 4 had not been read, and the overview of the standard that was read (arXiv 2510.13867) says the conformance part "specifies the requirements for generating a JPEG AI-compliant reconstruction" and "considers the possibility that a decoder is conformant to the standard without having bit-exact reconstruction", with "a certain leeway" — which points to a criterion around a reference, though the overview does not state one. SPEC §7 (wai.image.jpegai and the standard classes) now states what was and was not read, that the capability is a candidate for StandardTolerance if Part 4 sets such a criterion, and that its treatment differs from the StandardUnclassified codecs' and film-grain AV1's by this revision's choice, not by anything found in the standards. No class or runtime behaviour changes.

  • decode_envelope_strict and Wai::replicate_is_sound now apply SPEC §7 in two steps (codecs::replicate_refusal): the capability's registry class first, then the pinned prior. Before, they read only the prior's declared contract string, so a replicate envelope over wai.image.jpegai or a float codec passed when its prior declared an integer contract; it is now refused. API: new ReplicateClass::NeuralEntropyExact variant (an exhaustive match must add it), DeterminismTier, ReplicateClass::{neural_tier, label}, codecs::{replicate_refusal, ReplicateRefusal} and Wai::replicate_refusal; Wai::replicate_is_sound now consults the registry (a floor capability with no prior now passes it; the free function container::replicate_is_sound is unchanged and checks only the prior).

  • Fix: wai wrap wrote intent = replicate over every capability, including wai.image.jpegai and the float neural codecs, which SPEC §7 says an encoder MUST NOT do, and it pins no prior, so its envelopes over an integer-exact neural codec were refused by the strict decode as well. It now takes --intent <intent> (default replicate) and writes replicate only where a sink accepts it with no prior — over the three standard classes; otherwise it refuses, says why, and writes nothing. The same holds for a --fallback capability, since a sink that lacks the primary decodes through it (SPEC §4 step 5): wai wrap wai.image.jp2 … --fallback wai.image.jpegai is refused unless given another intent. SPEC §7 now says the class a sink checks is that of the capability it decodes through. API: codecs::unpinned_envelope_intent. The library test checks every registered capability against the sink's own check; tests/cli_wrap_intent.rs runs the binary, which needs the codecs feature, which no CI job builds.

  • CI: new wai-neural-gates.yml runs the two model-free neural-sink tests, strict_decode_gates_unsound_replicate (the decode_envelope_strict gate) and resolve_checked_verifies_prior_hash, on the Linux runner, and fails unless both ran. No workflow ran them before: they need the neural feature, which wai-conformance.yml does not build, and wai-neural-e2e.yml selects only the reference-match tests. The job runs in an Ubuntu 24.04 image: the ONNX Runtime binary the ort crate downloads needs glibc 2.38 and a GCC 13 libstdc++, and in the runner's default 22.04 image the test binary fails to link.

  • Wording corrections, each checked against its source:

    • The claim that byte identity holds between sinks running the same decoder build (the StandardTolerance and ReplicatePromise docs, SPEC §7) had nothing behind it and is removed; replicate over those classes does not promise byte identity across sinks.
    • JPEG and JPEG 2000 were listed with Opus and JPEG XL as having a stated bound. The clauses that would state them were not read (T.81 leaves the IDCT's accuracy to its Part 2; only the table of contents and terms of ISO/IEC 15444-4 were read), so SPEC §7, the extension and the registry docs now say so and give no bound.
    • The JPEG AI overview is quoted instead of paraphrased: it says Part 4 "considers the possibility" of conformance without bit-exact reconstruction, and does not itself state a criterion on the reconstruction.
    • "Before v0.4.0" in the registry comments and "until v0.4.0" in the extension were wrong: the replicate classes and wai.image.jpegai were both added within v0.4.0. They now say "earlier in v0.4.0".
    • determinism-tiers §2 described ISO/IEC 23001-11's decoding-operation reduction as in-band metadata with which a decoder degrades its output after a conformant decode. The 4th-edition CD text (WG 3 N1864) says otherwise: a decoding operation reduction request goes from the receiving device to the remote encoder, which sends a less complex bitstream, and complexity metrics let a decoder vary its operating frequency; neither changes the output of a decode. §2 now sorts every metadata set that text's Introduction names by what it changes. Attenuation-map post-processing, display adaptation (the RGB components of reconstructed frames rescaled, saturated or, for contrast enhancement, set to zero or the peak signal before display) and cross-segment decoding (an enhanced picture used as a reference) change the decoded medium, and the post-decode rule applies to them. Decoding-operation-reduction and attenuated-video requests, and low-power encoding, change the payload before it arrives; the rule about "honouring a hint" is replaced by one about such a payload: a tier, hash or receipt over it covers that payload, not the one it stands in for. Media selection and complexity metrics change neither. §2 also says what the metadata is: power-saving metadata stated in advance, whose energy quantities are expected or potential savings (clauses 7.4 and 8), not energy that was spent. energy-binding §7.1–§7.2 and task-fidelity §4 follow; §7.2 now names the parties to op_requested and op_committed as the requesting and the acting party, where "sender" and "receiver" had them reversed for a DOR request. The transport landscape research note marks its earlier reading as superseded in a note under its provenance header that names every passage repeating it.
    • int_entropy.rs cited SPEC §6 for replicate-soundness; it is §7.
  • Correction to the JPEG AI entries (SPEC §5 row and the decoder entry below): the dequantisation, latent refinement, post-filters (including the eICCI network) and colour conversion run in IEEE-754 float with a fixed evaluation order, not in integer; and the end-to-end test bar is max|Δ| ≤ 2 (recorded max|Δ| = 1), not 1 LSB.

  • Fix: wai.image.jpegai decoded codestreams it does not implement. The exact decoder checked only the picture size and latent channel counts, so a 256×256 codestream with β ≠ 0, another model_id, another synthesis transform or another eICCI model selection passed that check and was decoded as if it had the implemented configuration; the float ONNX path was no better (it applied β to the entropy parameters but not to dequantisation). The earlier statement that every per-image quantity is derived from the bitstream did not hold while β is not implemented; β ≠ 0 is now refused. jpegai_decode::check_codestream now checks every header field either path hard-codes, and both paths refuse a codestream that fails it. The exact decoder also refuses to run in a non-default floating-point environment (fp_env_is_default). The committed reference codestream (wai-rs/tests/vectors/jpegai/) lets the library tests exercise the check without fixtures.

  • Fix: the JPEG AI picture-header parser mislabelled a flag. The bit after each component's GRFS channel flags is synthesis_tile_enable; the LSBS flags are the first two bits of the tool header (parse_lsbs_flags). It also read c_ver / c_hor when s_ver / s_hor is 2, where the reference codes neither; the float path's parser shared that defect and dropped the separate chroma β.

  • Float reproducibility stated precisely (SPEC §7, the extension, DeterminismClass): IEEE-754 basic operations are correctly rounded, and float decodes diverge across hardware through what a runtime chooses per device and through the floating-point environment. replicate still requires integer arithmetic, because those conditions belong to one build and one process.

  • The extension now also states what a receipt can hash under each tier, why transmitting entropy parameters stops at entropy-consistency, which capabilities are offered at decode-equivalence and at what cost, and the evidence behind each claim — which tests skip, which CI does not run, and which matrix legs actually ran (the Windows x86_64 leg has not run since 2026-08-27).

Reproducible-reconstruction binding — a C2PA/JPEG-Trust hard binding over pixels

  • New wai.* provenance capability: reconstruction_binding (feature provenance) + [extensions/reconstruction-binding.md]. A cryptographic hard binding over the deterministically decoded pixels of a neural-coded asset — which such media normally can't carry, because a neural decode isn't reproducible (JPEG AI's normative conformance point is the entropy-decoder output, not pixels; its multi-branch design reconstructs one codestream several ways). WAI's byte-exact decode restores a portable decoded-pixel hash. SHA-256 (the C2PA default) over the canonical RGB8 buffer plus a descriptor pinning the reproducible decode; carried as a custom C2PA/JPEG-Trust assertion (science.transaction.wai.hash.reconstruction) beside the standard byte-binding. A substrate for machine-readable AI-content marking (e.g. EU AI Act Art. 50).

wai.image.jpegai now dispatches to the byte-exact decoder

  • decode_envelope prefers the deterministic jpegai_decode::decode when the model directory ships the integer weight bundle (weights/*.json + norm.json), falling back to the ONNX-runtime decode otherwise — existing bundles keep working. wai.image.jpegai is now registered in SPEC §5 (it was a live capability missing from the normative menu).
  • The cross-arch byte-equality matrix now covers the merged decoder's entropy stage (byte-exact-conformance), and that crate's build — broken on a moved rans.rs include — is restored.

JPEG AI — a byte-exact codestream→RGB decoder (jpegai_decode::decode)

  • wai::jpegai_decode::decode(codestream, &DecoderWeights, &NormTables) -> Rgb reconstructs an ISO/IEC 6048 JPEG-AI codestream to RGB through a fully integer entropy front end and a deterministic value/synthesis/post-filter path — one pinned reconstruction, reproducible byte-for-byte on every machine, so the decoded pixels have a portable hash. No per-image fixtures: every per-image quantity is derived from the bitstream; only checkpoint constants come from a sink-supplied model bundle.
  • Determinism profile: the entropy decode (mANS), the dequant, and the derived entropy inputs (skip mask, σ index, RVS band, dequant gain) are integer-exact against the reference; the PTQ value/synthesis stages are deterministic and land within 1 LSB of the ISO reference decoder. Distinct from the in-house wai.neural.int_* codecs (all-i64, hash-portable): this decodes a published external standard's codestream deterministically.
  • Modules: jpegai_decode (the composed entry point) over jpegai_bitstream, jpegai_mans, jpegai_pipeline, jpegai_synthesis, jpegai_icci (feature neural_int). Inputs are regenerable via tools/wai_jpegai_build_e2e_inputs.py from the JPEG-AI reference-software assets, which are kept locally and are not in this repository; the extraction is published under WAI's versioned models root (jpegai/v1/). The whole chain — codestream → RGB — is exercised end to end against the reference PNG by wai-jpegai-e2e.yml, which fetches those inputs pinned by SHA-256.

JPEG AI — a live, self-sufficient wai.image.jpegai decode (2026-06-22)

  • neural::jpegai::decode(codestream, &JpegAiModel) decodes a real ISO/IEC 6048 codestream to RGB with no per-image schedule fixtures: every per-image quantity is derived from the bitstream, and only checkpoint constants come from a sink-supplied model bundle. Wired into decode_envelope — JPEG AI is now a dispatchable capability, not just reference/conformance support.
  • The two derivations that closed it, traced bit-exact from the reference: the residual coding mask is sigma_skip > 382 (the σ surface before the rvs addition — on the bucketed index it looks ambiguous, on sigma_skip it is exact, 0 violations / 65536 positions); the dequant is resi × rvs_dq[grfs[ch]][lk] / 2^16 / scaler_vec[ch], with grfs determined the same way the σ rvs does so the per-channel scaler_vec is consistent. Matches the reference decoder's PNG to within rounding (max diff 1), checked by tests/jpegai_live.rs on a machine holding the local JPEG-AI assets; no CI job runs it (its model bundle is not in the repository).

Film reel — wai.film.reel, cuts over one world (2026-06-17)

  • The editor's layer over wai.film.linear: a WREL embeds one sealed world
    • a sequence of shots (each its own camera track + frame schedule) played back to back. Cutting wide→close, or one beat from two angles, is film grammar; a reel carries it as a few kilobytes of camera keys, the world shared across every shot. Conformance is reel-equivalence — film-equivalence extended over cuts.
  • Shots MAY revisit earlier world ticks (a cut back in time from a new angle), so the world is sampled at the sorted/de-duplicated union of all frame ticks — convergence still holds because replay-equivalence is order-independent.
  • Reuses the film machinery (shared camera interpolation + pose bytes) + the FilmReceipt profile (identity = reel_hash); wai_meter grows a .wrel branch. Registered + tiered Lossless. Corpus vector coverage (the character beat cut three ways, incl. a cut back in time) joins film-conformance/, verified by wai_film_vectors on the four-platform matrix. SPEC §5 row + extensions/linear-film.md §7.

Linear film — wai.film.linear (2026-06-16)

  • "A film is a sealed, camera-on-rails world." A generated film ships not as pixels but as the world that produces it (an embedded sealed wai.world.replay) + a camera track (keyframed pose over the world's ticks) + a frame schedule (which tick each frame samples). The sink renders the frames — lever 3 (instructions-at-the-sink) for the synthesizable slice of "video" (animation / CGI / motion graphics). The conformance corpus carries whole films in ~1 KB.
  • wai-rs film feature (reuses the world engine + Fx floor + blake3): the WFLM container (contract + embedded WWLD + camera track) and the deterministic reconstruct contract. Conformance is film-equivalence — per-frame BLAKE3(world_state_hash(frame_tick) || interpolated camera pose) is bit-identical on every machine. It composes the embedded world's replay-equivalence (inherited unchanged) with the camera track in the wai.det.fixed64 floor; the render (pixels) stays the sink's.
  • The world engine gained one general primitive: collect_checkpoints_ sampled / run_sampled_dispatch — read the state hash at an arbitrary frame cadence, independent of the contract's checkpoint cadence (a renderer samples the timeline at its own frame rate).
  • Corpus film-conformance/ + wai_film_vectors gen|verify (dolly, two-shot, crane-up) — all pass, on the four-platform CI matrix. Registered
    • tiered Lossless (film-equivalence is exact). FilmReceipt (per-frame Merkle leaves) + a wai_meter .wflm branch emitting measured joules.
  • Spec PROMOTED to extensions/linear-film.md, SPEC §5 temporal-content row, content-taxonomy updated (animation-film → ✅). The biggest commercial wedge in the taxonomy, now shipped.

Music as instructions — wai.audio.score, LIVE at /score (2026-06-14)

  • The deepest audio lever: a score carries no recording, only note events — ship the composition, not the recording. wai-rs score feature (reuses the world Fx floor + blake3): the WSCR container (contract + note events) and a trig-free deterministic reference synth — a phase accumulator drives a piecewise-linear oscillator (saw/square/triangle/pulse) shaped by a linear ADSR, all + − × ÷. Pitch is a per-sample phase increment authored off-line (no exponential in the floor). mixdown_hash is the mixdown-equivalence quantity, the music counterpart of spatial audio; a soundfont/neural instrument is the sink's presentation.
  • Corpus score-conformance/ + wai_score_vectors gen|verify (scale, chord/polyphony, timbre-changing arpeggio) — all pass. Registered + tiered Lossless (exact reference mix).
  • Browser sink ScoreSink (wai-web) + /score page, LIVE: synthesizes the score in-tab, verifies the mixdown hash vs the corpus, draws a piano roll, and plays the reconstructed PCM via Web Audio. wasm pkg uniquely named wai_score.
  • Spec PROMOTED to extensions/score.md (site mirror), SPEC §5 row, content-taxonomy updated (music → ✅). Completes the audio surface: recorded objects (audio.scene) and synthesized notes (audio.score).

Neural subsystem reconciled onto main (2026-06-14)

  • Wires the full learned-image/audio decoder family into neural/mod.rs and brings the categorical rANS (decode_categorical) it needs: hyperprior (mbt2018_mean), checkerboard context (elic), channel-autoregressive (mlicpp/mlicv2/cmic), integer-exact (int_synth), glc (VQ), inter-frame video (dcvc_rt/dcvc_fm), snac audio — plus the ISO/IEC 6048-1 JPEG-AI entropy + codestream
    • σ/value/filter port (jpegai_*).
  • Restores the semantic / generative path (semantic.rs, CLIP-similarity conformance, ModelRequirement.min_sim/sim_metric) and the shared deterministic ONNX loader (ort_session.rs), behind the semantic feature.
  • Re-adds byte-exact-conformance (cross-arch rANS + int_synth + JPEG-AI mANS goldens) and the model export/encode tooling under tools/.
  • All features build + test green together (neural + semantic + the interactive/temporal cargo classes); clippy clean.

Asset provenance — wai.asset.provenance, LIVE at /provenance (2026-06-13)

  • Lever 4 — the "verifies whatever it carries" half. For content that cannot be reduced to instructions (a photo, a recording), WAI does not reconstruct — it carries the opaque bytes content-addressed and attaches a signed, energy-accounted lineage. AssetReceipt (provenance.rs, provenance feature): a JWP group whose objects are processing steps (op + input/output content hash + work + measured joules), Merkle-bound, Ed25519-signed, with chain-linkage verification (each step's input = the previous output, final output = the asset hash) and parent_receipt_hash chaining. from_json so a verifying sink can re-parse it.
  • wai_prov tool: synthesizes a real image, builds a 3-step lineage (origin → PNG master → published JPEG) with the real measured CPU energy of each encode (IOReport via macmon, no sudo), wraps the JPEG as a content-addressed wai.image.jpeg envelope, and emits the signed receipt (origin 0 + PNG 0.43 J + JPEG 0.14 J = 0.57 J measured).
  • Browser sink — AssetProvenance (wai-web) + /provenance demo, LIVE: recomputes the asset's content hash in-tab and compares to the receipt (tamper-evidence — a "corrupt one byte" button flips it red), verifies the Ed25519 signature + Merkle chain, shows the energy- accounted lineage, and hands the carried JPEG to a native <img> (the browser decodes it; WAI does not). wasm pkg uniquely named wai_prov.
  • Spec PROMOTED to extensions/provenance.md (site mirror). It is a receipt profile about a carried asset (like jwp-receipts), not a §5 payload codec — the asset keeps its own capability (wai.image.jpeg). Completes the thesis: carries whatever it can reconstruct, verifies whatever it carries.

Cargo-class polish wave — real cloud, EWA render, receipts, telepresence (2026-06-13)

Four follow-on builds across the new cargo classes:

  • Real-SPZ-derived 4D clip (wai-spz-gen 4d): the bloom clip animates 1200 genuine SPZ [name removed 2026-09-30] Gaussians (the same cloud the static wai.splat.spz asset ships, round-tripped through the SPZ library) — real cloud, authored motion. LIVE on /volumetric. Honest framing: the win is a verifiable per-frame state, not capture fidelity.
  • WebGPU EWA render on /volumetric: the page now renders the reconstructed cloud with a real anisotropic EWA splat pipeline (the same one the worlds demo uses) — per-Gaussian covariance projection, conic, 3σ bounding quad, premultiplied blend, back-to-front sort — with a 2D alpha-disc fallback where WebGPU is unavailable. The hash-verify badge is render-independent. Confirmed on WebGPU (a browser [name removed 2026-09-30]) + 2D fallback.
  • Per-class receipts with measured joules: HapticReceipt (haptic/receipt.rs) + Splat4dReceipt (splat4d/receipt.rs) — the worlds/audio JWP receipt profile transposed to touch (per-channel blocks) and volumetric (per-frame blocks), reusing world::receipt:: merkle_root. New wai_meter bin measures real CPU energy (IOReport via macmon, no sudo) for haptic/splat4d reconstruction and emits a signed, self-verified receipt carrying the measured joules_micro; wai_world_meter now also dispatches feed0/skeletal0, so feed and avatar worlds get the same measured SessionReceipt. Every cargo class now has a measured-energy receipt path (haptic 57 µJ, splat4d 0.21 J for 1200 Gaussians × 17 frames, feed/avatar mJ-scale).
  • Avatar telepresence — LIVE at /telepresence: a live skeletal world of N avatars over the existing worlds relay. New SkeletalActor (wasm) signs CREATE + SET_ROTATION CausalOps; the worlds transport (relay-first + BroadcastChannel fallback, hello/sync/op/hash, boundary-hash convergence) is reused verbatim. Each tab creates and animates its own arm rig; sinks converge on the pose hash ("poses agree @ tick N"). Verified on prod through the real relay with two isolated contexts — identical pose hashes, zero errors.

Volumetric / 4D splats — wai.splat.4d, LIVE at /volumetric (2026-06-13)

  • Phase 5 complete — the roadmap frontier. A wai.splat.4d is a temporal Gaussian splat: the static splat extended to time. Each Gaussian carries a keyframed track (position / scale / rotation / color / opacity); the sink interpolates the cloud at frame N in the wai.det.fixed64 floor. Conformance is frame-state-equivalence: the reconstructed Gaussian set at frame N hashes identically on every sink, no tolerance — the temporal counterpart of the static splat.
  • wai-rs splat4d feature (reuses the world Fx floor + blake3, no codec/ML deps): the W4DS container (contract + per-Gaussian keyframe tracks, 14 Fx per key) + a deterministic reconstruct (Fx linear interpolation, hold outside the track). Trig-free (quaternions interpolate component-wise; the sink normalizes for rendering). frame_hash = the frame-state-equivalence quantity; clip_hash = frame-independent identity. Registered + tiered Lossy (consistent with wai.splat.spz — the splat representation approximates the captured scene, even though the reconstruction is frame-state-exact).
  • volumetric-conformance/ corpus + wai_splat4d_vectors gen|verify: orbit (two Gaussians swap places), pulse (scale + fade), morph (color shift) — every frame's hash checked, all pass.
  • Browser sink — Splat4dSink (wai-web) + /volumetric demo page, LIVE: reconstructs the cloud in-tab and verifies every frame's hash against the corpus; a frame scrubber re-derives the cloud, drawn as painter-sorted alpha Gaussians (a sink with WebGPU renders the same set with the full EWA pipeline — presentation). wasm pkg uniquely named wai_splat4d.
  • Spec PROMOTED to extensions/volumetric.md (site mirror), registered in SPEC.md §5 (wai.splat.4d). Honest about size: the win is content-addressing + dedup + a verifiable per-frame state, NOT payload reduction (a real cloud is MB-scale) — stated plainly.
  • The cargo set is complete across the medium: sight (worlds/replay), hearing (audio/mixdown), touch (haptics/sample), live state (feed/replay), a moving body (avatar/pose), and now a moving volume (splat.4d/frame-state) — one wai.det.fixed64 floor, one verification.

Avatars / digital humans — wai.avatar.pose, LIVE at /avatar (2026-06-13)

  • Phase 4 complete — digital humans: motion as instructions. An avatar is a skeletal world — the worlds container/op-log/ordering/ sealing/state-hash/receipt-chain, with the step reduced to the identity. Conformance is pose-equivalence (the per-bone local transforms hash identically), a special case of replay-equivalence.
  • wai.world.sim.skeletal0 (in wai-rs world feature): a bone is a parent[16] + translation[3] + rotation-quaternion[4] + scale[3] (all wai.det.fixed64). Mutations: CREATE / DELETE / SET_ROTATION (the animation driver) / SET_TRANSLATION / SET_SCALE, all LWW. Trig-free floor: rotations are quaternions and the FK composition is + − × only; axis-angle authoring happens off-line. The forward-kinematics → world space and the skinning of a wai.mesh.glb are presentation (a pure function of the hashed pose), never in the hash. Registered + tiered Lossless; dispatched in run_sealed_dispatch.
  • avatar-conformance/ corpus + wai_avatar_vectors gen|verify: a sealed 3-bone arm wave, a sealed 5-bone humanoid pose, and three seeded adversarial-arrival shuffles of the arm wave — all converge, all pass.
  • Browser sink — SkeletalSink (wai-web) + /avatar demo page, LIVE: replays a sealed animation in-tab, re-derives the sealed pose hash (checked against the native corpus and the file's own seal), and exposes FK world-space joints + bone segments to draw the skeleton on a rotating orthographic canvas with a tick scrubber. wasm pkg uniquely named wai_avatar.
  • Spec PROMOTED to extensions/avatars.md (site mirror), registered in SPEC.md §5 (wai.avatar.pose). A moving body joins sight (worlds), sound (audio), touch (haptics), and live state (feeds) under one floor.

State-feeds — wai.feed.state, LIVE at /feed (2026-06-13)

  • Phase 3 complete — live state-feeds: state as instructions. A feed is a non-physics world — the same WWLD container, op log, canonical ordering, sealing, state hash, and receipt chain as interactive-worlds, with the simulation step reduced to the identity. Conformance is therefore replay-equivalence, reused wholesale: no new criterion.
  • wai.world.sim.feed0 (in wai-rs world feature): nodes are records of self-describing typed cells (Int i64, Fixed Q32.32, Text UTF-8 — hashed without a schema). Mutations: CREATE / DELETE / SET_CELL (LWW) and ADD_INT / ADD_FIXED (SUM — commutative accumulation). Wrong-typed / out-of-range / absent-node updates are deterministic no-ops. Registered + tiered Lossless in the central codecs registry; dispatched in run_sealed_dispatch.
  • feed-conformance/ corpus + wai_feed_vectors gen|verify: sealed scoreboard (SUM scoring + an LWW correction), telemetry (LWW gauge + SUM packet count, incl. a negative ADD_FIXED), market tape (LWW last + SUM volume), and three seeded adversarial-arrival shuffles of the scoreboard — all converge, all pass (hash equality).
  • Browser sink — FeedSink (wai-web) + /feed demo page, LIVE: loads a sealed feed, replays it in-tab with the same engine that made the corpus, and re-derives the publisher's sealed BLAKE3 state hash (the file's seal also equals the native corpus hash — both checked). A tick scrubber re-derives the feed at any tick; the record table renders the typed cells generically. wasm pkg uniquely named wai_feed (avoids the deploy fingerprinter's basename collision).
  • Spec PROMOTED to extensions/state-feeds.md (site mirror), registered in SPEC.md §5 (wai.feed.state). The broadcast profile (one authoritative publisher, many read-only sinks) is documented as a deployment shape of wai.world.live, not a new format. State joins sight (worlds/replay), hearing (audio/mixdown), and touch (haptics/sample) under one floor — verified the same way.

Interactive worlds — world feature + conformance corpus (2026-06-11)

  • New cargo class implemented: the interactive-worlds extension (spec draft staged in wai-transaction-science-web/research/, destined for extensions/interactive-worlds.md). Media that consumes input: a world is a content-addressed artifact, every event is a signed CausalOp, and conformance is replay-equivalence — identical BLAKE3 state hash at every checkpoint, no tolerance parameter. One container (WWLD) serves wai.world.scene / wai.world.replay / wai.world.live, differing only in op-log seal state; an input log and a multiplayer op log are the same object.
  • wai-rs world feature (pure Rust, no ML dep chain; deps: blake3 + ed25519-dalek): Fx Q32.32 fixed-point (wai.det.fixed64, the floor profile — bit-exact unconditionally), xoshiro256++ contract rng, signed/dep-gated ops with canonical (effect_tick, lamport, actor) ordering, coordination-free BLAKE3 node minting, WWLD section container with content-binding seal, Sim trait (the extension standardizes the contract, not a physics engine) + kinematic0 reference registrant, sealed Replayer + LiveWorld engines (buffering, past-tick re-derivation, equivocation rejection, receipted-checkpoint history sealing).
  • world-conformance/ corpus + wai_world_vectors gen|verify: sealed replays, LWW/SUM conflict batteries, dep-gap / past-tick / shuffled-arrival live streams, expected hashes. All vectors pass; clippy clean on the new code.
  • Spec PROMOTED to extensions/interactive-worlds.md (site mirror src/pages/extensions/), registered in SPEC.md §5: wai.world.scene / wai.world.replay / wai.world.live + asset media wai.mesh.glb (canonical glTF 2.0 Binary, Khronos — decoder sink/library-supplied); media: "world" noted in §3. Capability strings live in the central codecs registry (known_capabilities()), tiered Lossless — replay-equivalence is exact state reproduction, the simulation analog of lossless.
  • Second sim registrant wai.world.sim.ballistic0 (gravity + ground-plane bounce, per-node radius/restitution, G = −10, all Q32.32 — bit-exact through every bounce) proving the registration story: entered registry, dispatch, and corpus (ballistic_bounce vector) without one line of the extension changing. New run_sealed_dispatch routes on contract.sim; a missing sim reports the world inert (UnknownSimCapability), mirroring SPEC.md §4 step-6 semantics so callers can fall back per the envelope.
  • Native energy meter — measured joules, no sudo (2026-06-13): corrected a wrong assumption that real energy needed RAPL/sudo (unavailable on the arm64 prod box). On arm64 macOS [name removed 2026-09-30] the SoC energy counters are readable through IOReport with no privileges (the source macmon/asitop use; verified euid≠0 reads CPU/GPU/ANE power on the arm64 machine [name removed 2026-09-30]), and proc_pid_rusage gives exact per-process retired cycles/instructions. New wai_world_meter bin (world feature): brackets a tight replay burst of a sealed .wwld against an idle baseline, samples CPU power via macmon, and reports the marginal measured CPU energy — e.g. the truss replay metered at ~512 µJ / replay (~4.3 µJ/tick) at 3.2 W marginal, a real number that fills a receipt's joules_micro where a native sink runs. The burst runs on N threads so the marginal draw clears a shared box's idle fluctuation (single-thread 512 µJ vs 10-thread 524 µJ/replay cross-validates the method — per-replay energy is invariant to parallelism). With a second arg the meter emits a full §8 SessionReceipt carrying the MEASURED joules + exact work_steps, signed and self-verified — real measured energy end to end. Spec §8 + the /worlds page corrected: the sink-metering path needs no sudo (IOReport on arm64 macOS [name removed 2026-09-30], RAPL on x86); the browser estimate is the fallback, not the ceiling. clippy clean, 94 tests green.
  • Phase 2 begun — haptics (wai.haptic.signal) (2026-06-13): touch as instructions. Spec drafted (research/haptics-draft.md), capability registered + tiered. wai-rs haptic feature (reuses the world Fx floor, no codec/ML deps): the WHAP container + a deterministic reconstruct — per channel, a keyframed intensity/sharpness envelope (the Core-Haptics pair every actuator API maps to) linear-interpolated in Fx and quantized to u8. Trig-free by construction (amplitude + sharpness, not a modulated carrier), so the floor reconstructs it exactly with +,−,×,÷ alone. signal_hash is the sample-equivalence quantity. wai_haptic_vectors gen|verify + haptic-conformance/ (ramp, thud, dual-rumble pulse) — all pass, hash equality. 104 tests, clippy clean. Next: Gamepad/Vibration browser sink + /haptics demo, then promote. This completes the sight → hearing → touch trio's engines.
  • Spatial-audio: receipt profile + spec PROMOTED (2026-06-13) — Phase 1 complete. audio_scene::SceneReceipt (the worlds receipt transposed to audio): identity scene_hash, the JWP group's objects are the mix's PCM blocks (BLAKE3("wai:audio-block")), Merkle-bound with JWP's exact domains, one Ed25519 sig over scene_hash + root + blocks + work_mac (samples × objects, exact signed) + carried joules_micro; parent_receipt_hash chains renders. Reuses world::receipt::merkle_root — minimal new code. 2 receipt tests (101 total). Draft promoted to extensions/spatial-audio.md (+ site mirror, SPEC §5 row + extension pointer) — and the canonical spec corrected to the as-built sqrt_constpower pan (sqrt-only, no transcendental tables) and the real receipt shape, since registration froze the format. clippy clean; spec page live. Phase 1 (spatial audio) DONE: spec · register · mixer · corpus · live /audio sink · receipt · promoted. Next: Phase 2 — haptics.
  • Spatial-audio browser sink — LIVE at /audio (2026-06-13): wai-web AudioSink (wasm-bindgen): parse WSCN, load the corpus sources, run the exact deterministic mixdown, expose mixdown_hash_hex + scene_hash_hex + mix (i16 PCM) + object_xz(t). The /audio page (site-styled, in nav) loads a scene, verifies the mixdown hash in-browser against the corpus (mixdown-equivalence, the same proof worlds does for state), plays the bit-exact stereo mix via an AudioBuffer, and shows a top-down radar of the objects panning. Proven on prod: all three scenes verify, zero errors — wasm in a browser [name removed 2026-09-30] mixes byte-identically to native. Honest split kept: plays the exact stereo floor; binaural/HRTF noted as the richer optional presentation.
  • Spatial-audio conformance corpus (2026-06-13): wai_audio_vectors gen|verify + audio-conformance/ — the mixdown-equivalence analog of the worlds corpus. Three vectors (static stereo pair, distance near/far, keyframed left→right sweep) ship a .wscn scene + a .sources.bin (decoded mono tones) + expected scene_hash and mixdown_hash; verify mixes each and checks hash equality, no tolerance. All pass; clippy clean.
  • Spatial-audio reference mixer — mixdown-equivalence (2026-06-13): wai-rs audio_scene feature (reuses the world Fx floor + blake3, no codec/ML deps): the WSCN container (scene contract + object table
    • asset table + optional seal, mirrors WWLD) and a fully deterministic mixdown — Fx position interpolation, inverse_clamped distance attenuation, sqrt_constpower pan (sqrt only, no transcendental tables), Fx accumulate, round-half-away i16 quantization in integer space. mixdown_hash (BLAKE3 over canonical i16LE PCM for a declared layout) is the mixdown-equivalence quantity; scene_hash is the identity. 5 tests prove it: centered = equal L/R, hard-left = silent right, deterministic + layout-bound hash, distance attenuates, a moving source sweeps L→R + container round-trips. Sources arrive as decoded mono PCM keyed by content hash — decoding the wai.audio.* assets is the sink's job. clippy clean. Next: conformance corpus bin + Web Audio browser sink + demo.
  • Content taxonomy + cargo roadmap; spatial-audio Phase 1 begun (2026-06-13): mapped the full space of web-transported content WAI's approach covers (research/content-taxonomy.md) — a capture→synthesizable→executable→ symbolic spectrum × four levers (codec dispatch, neural prior, instructions-at-sink, determinism+receipts), with a per-row mechanism/extension/status table and the white space called out. A phased build plan (research/cargo-roadmap.md) takes each white-space class through the proven interactive-worlds recipe. Phase 1 kicked off — object-based spatial audio (wai.audio.scene): the audio analog of interactive-worlds (ship sound objects + a scene, render to the sink's layout; mixdown-equivalence = replay-equivalence with the tick replaced by the audio sample). Spec drafted (research/spatial-audio-draft.md, WSCN container, Fx mixdown, content-addressed wai.audio.* sources, stereo bit-exact floor + binaural perceptual tier, WAI2 recorded-mix fallback, JWP receipt profile); wai.audio.scene registered + tiered Lossless. 94 tests green. Remaining phases: haptics, live state-feeds, digital humans, volumetric/4D splats.
  • Real SPZ decode — wai.splat.spz end to end (2026-06-13): the splat gallery now renders a genuine SPZ [name removed 2026-09-30] file, not a mesh-derived stand-in. New wai-spz-gen crate depends on the field's spz reference library (the sink-supplied codec WAI dispatches to), encodes a 2,400-Gaussian cube cloud to a real .spz (validated by re-loading through the library), and wraps it as a content-addressed wai.splat.spz WAI object (nebula.spz.wai). gen-gallery adds it to the scene's asset table (role 1) beside the glb. Browser: WorldSink.asset_payload(name, env, cap) generalizes the §4.1 verify to any capability; the page gunzips (DecompressionStream) and decodes the SPZ bitstream in-tab — 16-byte header, non-interleaved arrays, 24-bit fixed-point positions, log scales, smallest-three quaternions, byte/255 opacity+color — into per-Gaussian world covariance for the EWA pipeline. Decoded count matches the encoded 2,400 (validates the layout); the cube silhouette + surface-aligned discs validate the quaternion math. Page copy corrected: the splat view decodes a real SPZ asset (no longer "derived from mesh"). Verified locally (2,400 Gaussians, zero errors); prod serves the identical artifacts.
  • Anisotropic EWA splatting (2026-06-13): upgraded the splat renderer from isotropic billboards to real 3D Gaussian splatting. Each splat carries an anisotropic 3×3 covariance (surface-aligned flat discs: wide in the triangle tangent plane, thin along the normal); the vertex shader projects it to a screen-space conic (Σ' = J·W·Σ·Wᵀ·Jᵀ with the perspective Jacobian), emits a tight 3σ bounding quad, and the fragment evaluates the conic — the textbook 3DGS pipeline, still CPU-sorted back-to-front and alpha-blended. Cam uniform carries view + proj separately (+ viewport/focal) for the Jacobian. Visibly sharper, surface-coherent splats; proven on prod, zero errors.
  • 3D render layer — PBR mesh + Gaussian splats (2026-06-13): the /worlds gallery now renders the content-addressed, hash-verified wai.mesh.glb two ways in a real 3D path (perspective camera + depth buffer, all in wai-web/demo/world.js, no wasm change): a solid PBR-shaded cube (flat normals from screen-space derivatives, Lambert
    • spec + sky-ambient, gamma) and a Gaussian-splat cloud — instanced camera-facing quads with a Gaussian falloff, back-to-front depth-sorted each frame (painter's order) and alpha-blended. The splat cloud is derived from the same verified mesh, making the thesis visible: one content-addressed asset, two sink-side renderings, presentation never touches the state hash. SPZ binary decode stays sink-supplied (stated honestly on the page); the renderer is the real deliverable. Two gallery vectors (gallery / gallery_splat); the 2D sim path is untouched. Proven on prod: both render, 768 splats from the cube, zero errors.
  • Receipt chaining + work-meter + wai.splat.spz (2026-06-12): three follow-ons. (1) SessionReceipt gains parent_receipt_hash (BLAKE3 of the prior receipt's canonical bytes — the JWP linkage) + receipt_hash() + verify_chain: successive sealed sessions of one world (shared world_hash, group_id+1) form an append-only signed timeline. (2) An honest energy meter: receipts carry exact, signed, deterministic work_steps (Σ node-steps, from collect_checkpoints_and_work) AND a separately carried joules_micro — never derived in the receipt; the browser supplies a wall-clock estimate at a declared coefficient (labeled "est@4W"), native sinks supply real RAPL, an unmetered sink signs 0. (3) Registered wai.splat.spz — Gaussian-splat scenes as an asset capability parallel to wai.mesh.glb (presentation only, never in the state hash; the win is content-addressing + dedup, not payload size; renderer sink-supplied). Browser seal_session(seed, id, joules, group, parent) chains via localStorage; the /worlds HUD shows chain #N ↳linked · N steps · ≈X mJ est@4W · ✓ re-verified. Proven on prod: two seals chain (g0 parent null → g1 parent = g0.receipt_hash), exact work 248 steps, est joules carried, zero errors. Spec §8 updated (chaining + the two-quantity energy model). 94 native tests, 10/10 vectors, clippy clean.
  • Asset path SHIPPED — wai.mesh.glb, content-addressed (2026-06-12, extension §4.1): worlds are made of WAI objects. New world::asset_content_hash (BLAKE3 wai:asset\x01 over a complete WAI envelope) + DOMAIN_ASSET; native tests PROVE the load-bearing claim — a scene with an asset table has the byte-identical state hash AND world hash as the same scene without it (assets are presentation-only, never in the state hash), and the asset hash binds the exact envelope. Generator gen-gallery writes a gallery.wwld scene + assets/cube.glb.wai: a real glTF 2.0 Binary cube (hand-written GLB writer) wrapped as a wai.mesh.glb WAI envelope and content-addressed in the table. Browser: WorldSink.assets() + asset_glb(name, envelope) (verifies content hash in WASM, rejects a mismatch, unwraps the WAI envelope to the .glb payload); the page decodes the GLB (minimal JS decoder) and renders the verified mesh as a spinning wireframe. Proven on prod: 1/1 verified ✓ · cube.glb, and the state hash stays fixed while the mesh spins — §4.1 made visible. 92 native tests, 10/10 vectors, clippy clean.
  • Session receipts + seal-to-replay SHIPPED (2026-06-12, extension §8 implemented): world/receipt.rs builds a SessionReceipt — a JWP profile, not a fork: op batches (one per checkpoint interval, content-hashed wai:world-ops\x01) are Merkle-bound with JWP's exact leaf/node domain separators, and one Ed25519 signature covers the world-profile payload (world hash + checkpoint state_hash list + carried microjoules). Joules are carried, never fabricated — the builder takes the figure; an unmetered session signs 0 and says so. New world_hash (BLAKE3 over scene = contract + tick-0 graph, the §7.4 shareable identity), collect_checkpoints (unsealed replay → checkpoint list). Browser: WorldSink.seal_session(seed, signer_id, joules) → SealedSession{replay_bytes, receipt_json, root_hash, …}; the /worlds page's "⏷ seal & download replay" turns a live, shared, edited world into a portable .wwld + .receipt.json, re-opens the fresh replay in a throwaway sink to prove it re-verifies, and shows the Merkle root + batch count in the HUD. Proven on prod: fracture a tower across the relay → seal → 3-batch receipt, valid sig, replay re-verified. 90 native tests, 10/10 vectors, clippy clean.
  • Cross-device multiplayer SHIPPED (2026-06-12, wai-relay/ + wai-web Actor binding): every tab/device is a signed actor. New Actor wasm-bindgen class (browser-held Ed25519 key; clicks author real CausalOps; Lamport discipline via observe_lamport); wai-world-relay reference transport (axum WS, per-room fan-out, retains the signed op log for late joiners, verifies every op's Ed25519 signature at the door with the same wai crate — it adjudicates nothing; sinks converge because the math does). Page transport is relay-first (room in the URL hash) with BroadcastChannel fallback. Deployed at wai.transaction.science/worlds-ws/* (launchd com.wai.world-relay, localhost:9670). Proven on prod with two ISOLATED browser contexts: point-blank axial shoves snap two members (23→21 nodes) and both contexts converge — "✓ hashes agree". Also fixed build.rs emitting libFLAC link flags regardless of features (pkg-config probe side effect): now gated on codecs.
  • Browser sink SHIPPED (2026-06-12, wai-web/src/world.rs + wai-web/demo/world.{html,js}): a wai.world.replay sink in a tab. NOT a port — the wai crate itself now compiles to wasm32 via the new default-on codecs feature (C codec chain gated; envelope + world engine build with --no-default-features --features world), and wai-web binds it (WorldSink: advance/particles/members/ state-hash/verify-seal; sim state stays exact Q32.32, f32 only for display). WebGPU renderer (instanced SDF circles + member lines). Proven in a real browser [name removed 2026-09-30] via Playwright: the 1,282-byte truss_fracture payload replays 120 ticks, the member snaps, and the browser's BLAKE3 state hash equals the native corpus hash byte-for-byte — replay-equivalence across arm64-native and wasm32 in a browser [name removed 2026-09-30], demonstrated, with the Ed25519 seal verified in-page.
  • Solver-backed registrant wai.world.sim.truss0 (2026-06-12, world/truss.rs): structural simulation — particles + distance members solved per tick by position-based dynamics (fixed 8-pass Gauss–Seidel projection in canonical member order), entirely in wai.det.fixed64 including a new floor-rounded Fx::sqrt (128-bit integer Newton). Members carry a yield strain measured on predicted positions before the solve; an overloaded member fractures as an emergent solver consequence — two actors pulling the same member in opposite directions (the SUM rule feeding the solver) snap it bit-identically on every machine (truss_fracture corpus vector). Registration constants: G = −10, ITERATIONS = 8, ground at y = 0, velocity damping 63/64/tick (static equilibrium). LiveWorld gained state()/current_tick() accessors (what a renderer reads). Where the float-based CAD solvers can't transplant (floats aren't cross-platform bit-exact), solver form does: iterative constraint projection in the deterministic profile.

Inter-frame video, categorical/VQ images, and real-model validation

  • wai.neural.dcvc_rt / wai.neural.dcvc_fm — inter-frame video decoder implemented (neural/video_dcvc.rs). "Requires a GPU" [name removed 2026-09-30] was a speed claim: inter-frame decode is a frame loop that propagates a reference feature, and ONNX Runtime runs it on CPU. The per-frame entropy decode reuses the CompressAI-validated hyperprior path; the temporal net is sink-supplied. Validated end-to-end (3 inter-frames, feature propagation confirmed).
  • wai.neural.glc — categorical / VQ decoder implemented (neural/glc.rs). Not the Gaussian-conditional family: codebook indices decoded via a per-position softmax → pmf_to_quantized_cdf (bit-exact with CompressAI's C++, unit-tested) → a bypass-free categorical rANS over a checkerboard → VQ synthesis. Surfaced that categorical entropy is the most determinism-sensitive path — a per-position CDF flips on ~1e-5 logit drift where Gaussian scale buckets don't.
  • Real-model validation beyond synthetic stand-ins. SNAC's genuine snac_24khz now exports (tools/wai_snac_export.py: zero-noise so the decode is deterministic at all + a reshape-free snake) and wai-rs decodes it to ~9e-7 of the torch reference. A real PTQ-trained, activation-calibrated int_synth model decodes bit-identically (max|Δ|=0) to its integer-forward reference (tools/wai_int_synth_train.py; also fixed a zero-gradient torch.floor bug in the QAT trainer).

Learned-image codec family — decoders implemented and validated

  • The reference crate now decodes the full conditional-entropy family natively, not just the factorized baseline: wai.neural.mbt2018_mean (mean-scale hyperprior, Gaussian-conditional), wai.neural.elic (checkerboard spatial context), wai.neural.mlicpp (channel-autoregressive multi-reference), and the integer-exact wai.neural.int_synth (all-integer transposed-conv synthesis — a decode that is bit-identical on every machine by construction, the first capability that can soundly back intent=replicate cross-hardware). The rANS + entropy decode live in wai-rs; the analysis/synthesis nets are sink-supplied ONNX.
  • Current-SOTA entries added as live routes: wai.neural.mlicv2 (MLICv2/v2+, the current SOTA learned image) and wai.neural.cmic (Content-Aware Mamba) both decode through the multi-reference core — their entropy model is the same channel-slice + checkerboard structure; the gains live in their sink-supplied transforms.
  • Validation against the reference codec. bmshj2018 and mbt2018_mean are checked byte-for-byte against pretrained CompressAI models (reconstruction matches to within final-layer float rounding); elic/mlicpp are checked end-to-end against minimal CompressAI- compatible models; int_synth against a constructed integer model. Surfacing this also caught (and the spec now documents) a real determinism hazard: a learned codec's entropy decode desyncs unless the encoder and decoder compute the scale buckets with the same model — float drift between two implementations of h_s flips bucket assignments and corrupts the stream.

Audio: SNAC hierarchical codec

  • wai.neural.snac — SNAC's multi-resolution RVQ, distinct from the flat single-grid RVQ runtime: N variable-length code streams at hierarchical rates, decoded via dynamic-arity ONNX inputs. Ubiquitous in low-bitrate TTS.

Removed: off-charter Python runtime (wai-neural/)

  • Moved wai-neural/ OUT of this repo to ../../wai-neural-prototype/. It was a parallel Python runtime (WAI4 generative-semantic container + CLIP-embedding payloads + SD-XL-Turbo regeneration + CLIP-similarity conformance) — a disconnected second implementation in the wrong language. The reference implementation is Rust (wai-rs); nothing referenced the Python package. The idea it proved — a behavioral/perceptual neural path that conforms by semantic similarity rather than a weights hash — is sound and belongs in wai-rs as a future Rust path alongside the bit-exact, prior-pinned path. The Python is kept only as a prototype reference (and it hardcodes a machine-local model path, so it never ran portably). This does NOT touch the Python model-export tools in tools/, which are legitimate dev tooling.

Neural condition: working end-to-end

  • tools/wai_encodec_encode.py / wai_encodec_decode.py — first working neural capability (wai.neural.encodec32): source encodes audio via locally-installed EnCodec, ships only the quantized tokens in a standard WAI envelope (236 MB of model weights stay on the source's disk, never enter the envelope); sink with EnCodec installed decodes. Measured: 5 s of audio → 2.3 KB envelope (141× compressed), model on the wire = 0 B.
  • Rust SDK gains wai inspect CLI: parses an envelope, resolves the capability + fallback against sink_capabilities(), and prints verdict (DECODABLE via primary / via fallback / INERT here).

Browser runtime (wai-web/)

  • New crate: wasm-bindgen wrapper exposing parse(bytes) → WaiEnvelope, pack(manifest_json, payload), and known_capabilities(). 107 KB wasm + 16 KB JS glue.
  • wai-web/demo/wai.js — thin JS shim with WaiRuntime.register(cap, handler) dispatch. Backends are pluggable (browser-native APIs, transformers.js, WebGPU compute, WebNN, ONNX runtime web) — WAI doesn't bundle any. The sink-side compute fabric is heterogeneous; the deployer picks per capability.
  • wai-web/demo/index.html — drop-and-decode demo with handlers wired to: createImageBitmap for wai.image.{png,jpeg,avif,jxl}, AudioContext for wai.audio.{opus,flac}, placeholders for wai.neural.* showing where transformers.js / WebNN / WebGPU plug in. Run with python3 wai-web/demo/serve.py.
  • 4 sample .wai files in wai-web/demo/samples/ covering PNG, JXL, Opus, and EnCodec for testing.

Spec: v1.1 multi-rendition envelope (SPEC.md §6.1)

  • New magic WAI2 for envelopes that carry multiple renditions of the same content (e.g. EnCodec + Opus, or AVIF + JPEG-XL). Reframed from "graceful fallback for missing capability" to policy selection — in a self-contained ecosystem every sink has every required capability; renditions exist so the sink picks by compute budget, bandwidth, or runtime preference (browser-native vs WebGPU vs WebNN vs native ML). v1.0 (WAI1) remains valid and unchanged.

v0.3.0

Pivot: WAI is the envelope + capability-dispatch + neural-shared- prior standard. The zeroth condition is a menu of registered SOTA codec capabilities (AVIF, JPEG-XL, PNG, JPEG, Opus, FLAC, AV1, zstd, XZ), not a custom codec. The Python prototype and all hand-rolled codec implementations from v0.2.x were removed; Rust is the single reference implementation and the SDK.

Standard

  • SPEC.md rewritten as a capability-menu spec. Mandatory floor: wai.image.png, wai.audio.flac, wai.text.zstd (every conforming sink MUST decode these). Recommended modern set: wai.image.jxl, wai.image.avif, wai.image.jpeg, wai.audio.opus, wai.video.av1, wai.video.av1.lossless, wai.text.xz. Neural capabilities (wai.neural.*) remain OPTIONAL and sink-advertised.
  • Container envelope (WAI1 magic + length-prefixed manifest + length- prefixed payload) and manifest schema are unchanged from v0.2.x.
  • Conformance: bit-exact decode-equivalence with each capability's reference library (libjxl / libavif / libopus / libFLAC / rav1e / etc.).

Rust reference (wai-rs/, crate wai 0.3.0)

  • cargo build --release produces libwai.dylib/.so/.dll (cdylib), libwai.a (staticlib), and libwai.rlib (Rust-native). The crate type list is ["lib", "cdylib", "staticlib"] — drop-in for any language's C-FFI binding.
  • Codec wrappers: image (PNG, JPEG via jpeg-encoder, AVIF via ravif + libavif-image for decode, JPEG-XL via jpegxl-rs), opus (RFC 7845 Ogg-Opus container so output drops into ffmpeg/VLC/browsers), claxon+flac-bound for FLAC, rav1e+dav1d for AV1 (lossy + lossless), zstd+xz2 for text.
  • Generated C header: include/wai.h is regenerated by build.rs via cbindgen on every build. SDK consumers get a typed header rather than hand-writing extern "C" decls.
  • Cross-platform linkage: build.rs probes pkg-config for libflac; the macOS-hardcoded .cargo/config.toml from v0.2.x is gone. Builds clean on macOS (Homebrew) and Linux (apt) without RUSTFLAGS.

C FFI / SDK surface

All wai_* functions return a wai_status_t:

code meaning
WAI_OK (0) success
WAI_ERR_INVALID_ARG (1) null pointer / mismatched length / out-of-range dimension
WAI_ERR_CODEC (2) codec-level failure (details in wai_last_error())
WAI_ERR_CONTAINER (3) bad magic / truncated envelope / unparseable manifest JSON
WAI_ERR_PANIC (4) internal Rust panic caught at the boundary (a wrapper bug — never UB into the C caller)

Every FFI function is wrapped in std::panic::catch_unwind so Rust panics never unwind into C (UB). av1_decode validates header bounds to prevent libdav1d's C-level abort(). wai_last_error() is per- thread, populated on any non-OK return.

Testing

  • 13 lib tests: per-codec round-trip + envelope round-trip
  • 21 FFI robustness tests: null pointers, length mismatches, dimension overflow, malformed magic, truncated payloads, bad JSON, invalid Opus sample rates, zero fps, garbage decoder input
  • 10 fuzz smoke targets: 1024 adversarial random byte sequences per decoder asserting no panic. Stable Rust, no cargo-fuzz toolchain needed. Found and fixed a real envelope-truncation panic.
  • wai_bench corpus walker: quick mode (4 images + 4 audio + 3 text + 2 video) for spot-checks; --full walks the entire corpus (169 images + 5 audio + 13 text + 12 video sequences = 588 codec runs in ~9.4 min). Per-codec aggregate stats: runs, ok / fail counts, WAI vs reference byte sizes, PSNR delta vs ffmpeg, encode time.
  • --report file.json writes a structured bench report; wai_bench_diff baseline.json head.json flags regressions on bytes (±5% default tolerance), encode time (±25%), or new failures. Non-zero exit on regression — ready for CI gating.
  • Hosted CI [name removed 2026-09-30] (.github/workflows/ci.yml): ubuntu + macOS matrix, apt/brew codec deps, cargo test --release, cdylib/staticlib artifact verification, quick bench, JSON report uploaded as artifact.

Honest codec measurements (vs ffmpeg reference, full Kodak/Tecnick/

CLIC/synthetic corpus = 169 images, plus SQAM audio + Silesia text + Derf CIF video)

codec runs size vs ref PSNR Δ vs ref notes
PNG 169 — lossless ffmpeg-interop ok
JPEG q=50 169 +12.7% +0.27 dB q-knob calibrated to libjpeg
JPEG q=80 169 +19.7% −0.31 dB residual gap is pure-Rust vs libjpeg-turbo asm
AVIF q=70 169 — — ffmpeg-interop ok
JPEG-XL lossless 169 — lossless bit-exact via libjxl
FLAC 5 −10.1% lossless WAI compression_level 8 vs ffmpeg default 5
Opus 64k 5 — — standard Ogg-Opus container, ffmpeg-interop ok
zstd-19 13 ±0.0% — byte-identical to system zstd
xz-6 13 ±0.0% — byte-identical to system xz
AV1 lossy 12 +28.3% — rav1e vs ffmpeg libsvtav1 (different encoders)

All 588 corpus runs and all 44 tests pass.

Removed

  • The Python prototype (wai/) and Python BD-rate harness (bench/). v0.2.x's hand-rolled wire format is dead; Rust is the single source of truth.
  • The webm_lottie_new/wai-rs/ pre-pivot Rust crate (Wasm-decoder-in- bundle model). Replaced by the clean wai-rs/ SOTA-wrapper crate.

v0.2.0 — superseded (historical record)

The v0.2.x direction was hand-rolling custom codec wire formats (DCT + quantization + rANS for image, MDCT for audio, I/P inter-frame for video). Two byte-interoperable reference implementations (Python + Rust) were built and cross-validated.

Why superseded: re-implementing JPEG/MPEG/Opus poorly is strictly worse than wrapping the field's SOTA libraries. WAI's actual value is the envelope + capability dispatch + the neural-shared-prior model, which v0.3 keeps. Custom codecs are gone; standard libraries are in.

What v0.2 had

  • SPEC.md defined byte-precise wire formats for image (8×8 DCT-II + YCbCr + JPEG-Q + zigzag + DC-Δ + rANS), audio (orthonormal MDCT + sine window + TDAC), video (I/P inter-frame with integer-pel motion).
  • Python (wai/) + Rust (wai-rs/) implementations, byte-equivalent on the lossless paths (rANS, container) and reconstruction-equivalent on the lossy paths.
  • Honest measured standing: image at JPEG-1992 parity (~5% behind libjpeg), audio uniform-MDCT (not perceptually competitive with MP3), video I/P at ~41% smaller than MJPEG floor but far behind H.264.

Licensing (unchanged)

  • Apache-2.0 for the standard and the reference implementation. Royalty-free implementation is a precondition for adoption (VP9/AV1 open-codec precedent; the H.264/H.265 royalty trap is avoided).