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
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]>
3560 lines
254 KiB
Markdown
3560 lines
254 KiB
Markdown
# 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).
|