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

3560 lines
254 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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