open-standards/wai/CHANGELOG.md
Transaction Science 1f0ab6ebf1
All checks were successful
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Successful in 6s
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 8s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 8s
wai-audit collector and player integration / does this change touch it (pull_request) Successful in 7s
WAI operation catalogue / does this change touch it (pull_request) Successful in 7s
wai-app and Create / wai-app · does this change touch it (pull_request) Successful in 15s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 14s
WAI conformance / does this change touch it (pull_request) Successful in 5s
WAI examples / WAI examples · does this change touch them (pull_request) Successful in 7s
wai-fleet service / does this change touch it (pull_request) Successful in 5s
WAI energy receipts / does this change touch it (pull_request) Successful in 18s
WAI JPEG-AI whole decode (pinned inputs, required) / does this change touch it (pull_request) Successful in 8s
wai-key-proxy / does this change touch it (pull_request) Successful in 8s
wai-ledger service / does this change touch it (pull_request) Successful in 5s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 8s
wai-quantum docs, course and locales / does this change touch it (pull_request) Successful in 6s
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Successful in 9s
WAI neural sink gates / does this change touch it (pull_request) Successful in 19s
wai-quantum for JavaScript / does this change touch it (pull_request) Successful in 7s
wai-quantum WebGPU member / does this change touch it (pull_request) Successful in 6s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 10s
wai-sink-android native sink library / does this change touch it (pull_request) Successful in 7s
WAI mbt2018_mean integer decode (regenerated vectors) / does this change touch it (pull_request) Successful in 11s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 7s
wai-web wasm32 / does this change touch it (pull_request) Successful in 8s
WAI Studio / does this change touch it (pull_request) Successful in 11s
wai-sink-swift native sink package / does this change touch it (pull_request) Successful in 17s
joule-code conformance gate / joule-moq-relay, every target (pull_request) Successful in 3m4s
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 3m16s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m5s
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Successful in 5m8s
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (pull_request) Successful in 3m22s
joule-code conformance gate / joule-moq-relay, its own energy on a counter (macos) (pull_request) Successful in 36s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (pull_request) Successful in 35s
wai-audit collector and player integration / collector · linux (pull_request) Successful in 1m8s
wai-quantum WebGPU member / member · DX12 and Vulkan (cbio-windows) (pull_request) Successful in 12m1s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (pull_request) Successful in 2m42s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 27s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 3m10s
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m42s
WAI operation catalogue / the catalogue and every door · linux (pull_request) Successful in 6m13s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 4m14s
wai-quantum WebGPU member / member · integrated GPU, Vulkan (framework-128gb) (pull_request) Successful in 10m34s
wai-fleet service / service · linux (pull_request) Successful in 2m26s
wai-ledger service / service · linux (pull_request) Successful in 2m2s
wai-key-proxy / proxy · linux (pull_request) Successful in 2m59s
wai-pack / wai-pack · linux (pull_request) Successful in 2m5s
wai-quantum docs, course and locales / the Studio's words in every locale · linux (pull_request) Successful in 26s
wai-quantum docs, course and locales / the course and the command line's languages · linux (pull_request) Successful in 2m45s
wai-quantum WebGPU member / member · GPU, Vulkan (jetson-hub) (pull_request) Successful in 22m31s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 7m26s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 2m2s
WAI examples / WAI examples · portable · linux (pull_request) Successful in 9m31s
WAI neural sink gates / the exact JPEG-AI decoder reports its path (pinned inputs, required) (pull_request) Successful in 2m52s
wai-quantum WebGPU member / member · GPU, Vulkan (thor) (pull_request) Successful in 24m59s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 2m16s
wai-quantum for JavaScript / javascript · linux (pull_request) Successful in 3m47s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness (pull_request) Successful in 3m57s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 3m53s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m57s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 1m6s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Successful in 8m6s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 9m36s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 14m34s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 6m37s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 5m30s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 19m54s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 5m27s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 12m3s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 10m30s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (pull_request) Successful in 22m30s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Successful in 1m2s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (pull_request) Successful in 1m52s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 12m16s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Successful in 1m20s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 17m11s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 30s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m10s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 3m54s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (pull_request) Successful in 7m52s
wai-fleet service / service · macos (pull_request) Successful in 43s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 30s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 6m52s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 9m7s
wai-ledger service / service · macos (pull_request) Successful in 1m26s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 17s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 20m26s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 43s
wai-pack / wai-pack · macos (pull_request) Successful in 4m19s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 2m38s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 13m34s
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Successful in 3m26s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 19m27s
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Successful in 2m5s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 23m32s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m27s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 7m32s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 37s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 19s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Successful in 11m58s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 5m11s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 7m35s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 3m25s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 7m20s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 12m0s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 10m54s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 17m29s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h30m12s
wai/toolchain-fixes, converted to the layout of the fix: no shared append point
The change of wai/toolchain-fixes (69f88e62f..074658780, 21 commits), on the fix branch: its
changelog entries as files of wai/changes/, its wai-conformance.yml edits as step files,
corpus files and test tags, and every other file as the branch has it.

- b04013914 holdings: the registry lists WHD1, the record's magic (§4.1)
- a184abb87 catalogue: upscale and tonemap are recorded in N3's verb list, and the doors' check knows them
- 8db31a778 wai mcp: MCP 2026-07-28, the stateless revision, beside the three initialize negotiates
- 052830194 wai.json: $schema, wai and name, and the schema of version 1 committed beside the reader
- 163fc2017 wai create: every template carries an AGENTS.md
- fe9b26fbc joule-moq-room: the example names the relay's WebSocket port, 4480, and one test holds every port
- 359b5ba9d quickstart: the archive carries WAI Studio, wai-ingest and wai-quantum
- 6334d88c8 joule-moq-room: the port table holds every daemon's default, and the listeners off until set
- d38610de6 wai.json: the two workflows that hold the schema to its reader run when the schema changes
- 179df52b7 catalogue: the two verbs joined the list after wai 0.4.0, not in toolchain 0.3.0
- 971cf5862 wai.json: a file of a later version is refused as that version before any member it adds
- 1a59a39f7 wai.json: the reader takes a whole number however it is written, and the tests run the name pattern
- d752fd9b4 wai.json: the release waits for the schema to be served, and version 1 is fixed by the first release that writes it
- c213ed9db wai mcp: -32022 names as supported only the revisions a request may name, never the one asked for
- 99b800f29 wai mcp: initialize negotiates 2025-11-25, the last revision with a handshake
- e33021c3f catalogue: the near-synonym list is held in every text that states it, and the doors' check knows up
- b99bb70be catalogue: the changelog records the near-synonym list held with the verb list
- a0beb2163 quickstart: the wai formula and manifest depend on wai-quantum's own instead of installing a second copy
- 0e5084031 quickstart: Studio's build leaves the checkout as it found it, and the examples workflow runs clean-machine.sh
- 4cc4ae3d7 joule-moq-room: the port test reads the gateway's example with the gateway's own reader, and starts the gateway from it
- 074658780 quickstart: the changelog names where Studio's build goes, not where it went before it shipped

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 01:45:34 -05:00

11366 lines
936 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

# WAI — changelog
Notable changes to the WAI standard and the reference implementation.
Newest at the top. WAI is a draft standard; until v1.0 freezes, the
wire format may change and such changes are recorded here.
## Unreleased
### The native sink through the conformance runner, and a result that admits an implementation only for a run that passed
- **Admitting an implementation** ([extensions/conformance-results.md](extensions/conformance-results.md) §4.1). A
relying party that admits an implementation on a `conform-result/1` record, such as a certification of a sink or a
gate in a build, requires the record accepted (§4), its run passed (`not_conformant`), its suite one whose corpus it
holds (`suite_unknown`), and its corpus digest that corpus's (`corpus_mismatch`). `wai verify --conformant [--root
<wai-dir>]` applies the rules against the corpus of a checkout. A record of a run that did not pass is a genuine
record, and `wai verify` without `--conformant` accepts it as before.
- **A sink kind.** `wai conform sink --suite native-sink` gives a native sink every session of
`live-conformance/native-sink/` with its answers withheld, and each configuration the corpus states a session
refuses; a case passes when the sink's answers, one RFC 8785 text a line, have the SHA-256 of the answers the corpus
states. `wai_native_sink_vectors answers [--c-abi] <case>` is the reference sink, through its module or through the C
interface the platform integrations link.
- **The native sink and the score decoder in CI** (`wai-breaking.yml`, the runner job): the native sink through its
module and through its C interface, and the score decoder (`decoder --suite score`), each run conformant and each
record admitted against the checkout's corpus. A planted sink that presents an object whose digest does not match
fails its run, on that case; its record verifies, and admission refuses it at rule 2. `tests/cli_conform_sinks.rs`
holds the same, and refuses a run against a copy of the corpus changed to suit the sink (`corpus_mismatch`) and a
record of a suite the build does not run (`suite_unknown`). The browser sink has no `sink` suite:
`wai_browser_sink_vectors verify` and `browser_sink_verify.py` hold its corpus, as before.
### Pin by default: an encoder pins the set a payload was coded against, and a verifier reports an envelope that does not
- **SPEC §3.1, *Pin by default*.** An encoder SHOULD pin the parameter set a payload was coded against wherever a
capability that decodes it is registered as decoding against one, whatever the intent. Only `replicate` over a
`NeuralIntegerExact` capability required a pin, so every other unpinned decode ran against whatever set the sink
held, and a set other than the payload's gave plausible output, not an error. An envelope that pins nothing there
still conforms in `1.x`, and a sink decodes it as before; a verifier reports it (`prior_unpinned`), informatively.
- **The report.** `Wai::check_unpinned`, `WaiMulti::check_unpinned` and `codecs::pin_by_default` make it. `wai
verify` now reads the envelopes (`*.wai`) under each path: it refuses one whose pin a verifier can tell is wrong
without holding a set (SPEC §3.1, §7), and notes `prior_unpinned`, which refuses nothing. `wai inspect` notes it,
and so does `wai wrap`, which pins no prior. `pin-conformance/unpinned.json` holds its vectors, reached by
`wai_pin_vectors verify` and by `pin_verify.py`, which reads the classes from the published registry export.
- **A record of a decode names the set it read.** A `reconstruct-session` decoder entry's `pin` (jwp-receipts
§2.4.12) now names the set an unpinned decode read, by the digest a pin over those bytes names, with the tier
`none`; it was `null`. The reference sink's base decode gives it (`staged::BaseDecode::set`, from
`int_payload::IntParams::read_pin`), and the sessions `stage_measure` seals carry it. The page's recorder writes the
same: `wai-web`'s `SessionHandle.decoderFor` takes the set the decode read (`StageBase.paramSet`) beside whether
it verified a pin (`StageBase.pinned`), and `measure_verify.py` names the set as the text does. The fixture
`staged-measure-conformance/fixtures/unpinned`, over the staged corpus's unpinned `wai.neural.int_hyper` base, holds
both recorders and both verifiers to it; every other committed session and golden pins its bases, so none of them
changes. A decoder entry sealed by a sink of the earlier text may carry `null` for an unpinned decode that read a
set, and a verifier cannot tell it from a decode that read none (jwp-receipts §2.4.12). `claim-conformance/`'s
vectors of an unpinned `wai.neural.int_hyper` decode name the set it read, and one more, appended, writes the base
in the earlier text's form: a stage hash over either is refused (`session_stage_hash_over_an_unpinned_base`,
`session_stage_hash_over_an_unpinned_base_earlier_text`).
- **SPEC §3.1 rule 2** says that an unpinned envelope conforms unless §7 requires the pin (`replicate_unpinned`).
- **Breaking:** `api:rust:wai::staged::BaseDecode::set`: a new member of a public struct, the set the decode read; a
struct literal or a pattern that lists every member adds it.
- **Breaking:** `wire:corpus:wai/staged-measure-conformance/goldens.json`: it gains the `unpinned` fixture's
evaluation, and its `dedup` record, taken over the objects of every fixture, now counts that fixture's too, so its
totals move; no other fixture's evaluation changes. A verifier reads the goldens of the corpus it checks.
### A decode reports the path that ran, and no tier lies between the two
- **The reference dispatch reports the decode path** ([extensions/determinism-tiers.md](extensions/determinism-tiers.md)
§5). `neural::decode_envelope`, `decode_envelope_strict`, `decode_multi`, `decode_multi_strict` and `decode_payload`
return a `Dispatched`: the output, the capability the payload decoded through (its own or the fallback) and the path
of it that ran, `DecodePath::Sole`, `Exact` or `Float`. `wai.image.jpegai`'s float decode through ONNX Runtime and its
exact decoder now say which ran. The C ABI's `wai_decoded_t` carries the path (`WAI_DECODE_PATH_SOLE`, `_EXACT`,
`_FLOAT`) and whether a `WAI1` envelope decoded through its `fallback`, so a native sink states them in its receipt.
`neural::check_reconstruction_binding` reads the path and the binding's descriptor
([extensions/reconstruction-binding.md](extensions/reconstruction-binding.md) §2): over a `decode-equivalence`
capability or the exact decoder it reports whether the hash holds; over the float decode it reports the binding as not
checked, and under a descriptor naming another reconstruction (another `decoderId`, format or size) as another
reconstruction, never as failed. The notes that the dispatch did not report the path are gone from both texts.
- **Where the decode path is tested.** `tests/jpegai_decode_path.rs` requires the result, the session claim and the
binding check to state the path. Its cases with one path run on committed inputs in `wai-neural-gates.yml`; a job
there fetches the pinned `jpegai/v1` bundle and runs the exact decoder with `WAI_REQUIRE_SOTA_VECTORS=1`. The float
decode needs the JPEG-AI reference-software assets, which are not in the repository, so its test runs locally only,
on a host that holds them, as `tests/jpegai_live.rs` does.
- **No tier between the two** (determinism-tiers §3, normative). A pin's tier vocabulary stays closed. A decode of a
`NeuralIntegerExact` capability in reduced precision or floating point is not a decode of it; a capability whose
payload holds several frame types takes the class of its weakest; and a clip digest, a stage hash or a reconstruction
binding over a sequence covers only frames each decoded at `decode-equivalence`, which a receipt checks per object.
These rules govern decoded media; generation-equivalence carries its own digest of the frames generated.
- **A session claim states its decode path** ([extensions/jwp-receipts.md](extensions/jwp-receipts.md) §2.4.12). A
decoder entry's `variant` is `exact`, `float`, or `null` for a capability with one path; a `NeuralIntegerExact`
capability's integer path may also be named by the lowering its registration names, as opus-dred §7.2 names
`dred-int/1`. Those three are the exact paths. Rule 13 refuses a `NeuralIntegerExact` capability named with a path
that is not exact (`session_variant_not_exact`); rule 14 refuses any path that is not exact under a pin of
`decode-equivalence` (`session_tier_exceeds_variant`); and against the stage claims, a stage hash over a base an entry
decoded at a weaker step, or over a base or layer an entry decoded by a path that is not exact, is refused
(`session_stage_hash_not_exact`), whichever units that entry presented. `claim-conformance/` gains vectors for each,
beside accepted ones for both `wai.image.jpegai` paths and for a registered lowering, appended after the earlier
vectors, which a test pins by digest; `claim_verify.py` and `differential.py` read the same rules.
- **determinism-tiers §1** records the five published routes to a reproducible stage, from the paper sections read, and
a line that reaches no tier: float synchronised on a tested hardware class (arXiv 2608.00483). **§7.1** cites the two
published records of float decode across devices (arXiv 2608.00483, 2606.28027) beside WAI's record. The research
notes `syfai-input.md` §5 and `2026-09-global-sweep.md` (its row of arXiv 2608.00483, G7, G9 and F5) are corrected
against the papers' bodies and the dispatch.
- **A catalogue-prior record** ([research/catalogue-prior.md](research/catalogue-prior.md)): a linear basis learned on
openly licensed films of one studio, measured against AV1, with its readings, its procedure, its decision rule
and when that rule was fixed. The rule refutes a linear or spectral catalogue basis and exact recurrence across titles
for a studio's catalogue; a learned prior is not measured. `tools/wai_catalogue_prior_verify.py` recomputes every
BD-rate and verdict from the readings and checks every figure the page quotes, in the conformance workflow.
- **Breaking:** `api:rust:wai::claim_receipt::Reason::SessionVariantNotExact`,
`api:rust:wai::claim_receipt::Reason::SessionTierExceedsVariant` and
`api:rust:wai::claim_receipt::Reason::SessionStageHashNotExact`: new variants of an exhaustive enum, which an
exhaustive match over `Reason` needs.
- **Breaking:** `api:rust:wai::neural::decode_envelope`, `api:rust:wai::neural::decode_envelope_strict`,
`api:rust:wai::neural::decode_multi`, `api:rust:wai::neural::decode_multi_strict` and
`api:rust:wai::neural::decode_payload` return a `Dispatched` in place of a `Decoded`; the output is its `output`.
The `neural` feature is outside the checked API, so the check does not report these.
- **Breaking:** `api:c:wai_decoded_t`: two fields after `fps_x_1000`, `path` and `fallback`, so the struct is larger. A
caller built against the old header is rebuilt against the new one.
### CI: a full runner disk fails a byte-equality leg as infrastructure, not as a divergence
- **CI:** each leg of the two cross-architecture byte-equality matrices (`wai-byte-equality.yml`, and
`wai-conformance.yml`'s `byte-exact`) measures the free space under its workspace, the runner's temporary directory,
TMPDIR and HOME before the checkout, and writes and syncs 32 MiB on each filesystem. Below 4 GiB, or on a write
refused for space, an I/O error or a read-only filesystem, the leg fails in a step named for infrastructure, with an
error that says no fixture was decoded. When a later step fails, the measurement and the write are made again, and
the failure is named infrastructure if the disk filled meanwhile.
### CI: WebAssembly runs under one pinned wasmtime with both its determinism settings, and the browser packages hold no relaxed SIMD
- **CI:** every workflow that runs a module under wasmtime runs wasmtime 50.0.1, pinned by version and SHA-256, with
`-W nan-canonicalization=y -W relaxed-simd-deterministic=y`: every NaN a float instruction makes is the canonical
one, and each relaxed SIMD instruction has one behaviour on every host. `wai-ops.yml` and `wai-vector.yml` ran
38.0.3, and the quantum and face jobs 49.0.2, each with neither setting. The repository checks
(`scripts/check-wasmtime-pin.py`) fail a workflow that pins another release or runs wasmtime without both.
- **CI:** `wai-web-wasm.yml` validates both of `wai-web`'s bundles and `reach/record/wai_web_bg.wasm` with relaxed SIMD
and threads disabled (`wasm-tools` 1.250.0, pinned the same way), so a relaxed SIMD instruction, a shared memory or
an atomic in any of them fails the job ([determinism-tiers](extensions/determinism-tiers.md) §5).
- The READMEs' commands that reproduce these jobs pass the same two settings. The quantum docs check holds a
`VAR=value` before a command a `ci:` block names to a value the workflow gives `VAR`, so a documented runner cannot
drift from CI's.
### CI: the maintainers' product names are checked on every pull request
- **CI:** the public-name check applies the maintainers' product names, held in the script, on every change, a pull
request included (`--maintainer-products-only`); the private list still runs on the push to main. CONTRIBUTING
states both.
### Contribution links: a packet taken after its sequence number's drop point is late, whatever the timer did
- **[live-contribution](extensions/live-contribution.md) §6.1:** a packet *arrives* when the receiver takes it. An
SRT packet that arrives at or after its own delivery time, before the first packet after it that has arrived is
due, has not been dropped (§4.1): it is delivered at once and is **recovered**, with a margin of 0. One that
arrives at or after that point is **late**, whether or not the receiver had dropped its sequence number before
taking it. **§6.2:** `margin_min` counts such a packet's margin as 0. **§6.3:** an SRT recovery is not bounded by
`window`. **§5.3 and §6.8:** a recovered packet is on time only with a margin above 0; the record does not count
the SRT packets recovered after their delivery time, nor say when a packet delivered at the first try was taken.
The verifier's rules and the corpus are unchanged.
- The reference SRT and RIST receivers resolve what has fallen due before they take each packet. Before, a
retransmission taken after its drop point with no timer tick between was delivered as recovered with a margin of
0, and with a tick between it was dropped and counted late. Unit tests now drive each receiver on a clock of their
own through every case.
- The reference SRT receiver's timer runs an ACK period (10 ms) after it last ran. It was counted from the last
datagram, so while datagrams came closer together than that it did not run until the first packet held fell due:
in a link's first window it sent no full ACK and no periodic NAK, and a packet whose first NAK or retransmission was
lost went unrequested until that window was nearly over.
- A recovered packet still held when a link ends keeps its margin, the time it arrived before its delivery time; it
was recorded as 0, which read as a recovery after the delivery time.
- `joule-contrib`'s SRT link tests over a lossy path use a window of a second, and no longer bound the recovery time,
which counts from when a packet was found missing and is not a property of the protocol (§6.3); the receivers' unit
tests now find each gap away from the clock's origin, so a recovery counted from anywhere else fails them. The tests
still require a margin above 0, the end-to-end sign that every packet recovered was taken before its own delivery
time. Their failure in CI (nothing dropped, every payload whole and in order, the recovery or the margin out of
bounds) fits the ordering defect above, a busy runner's lag (the receiving task hundreds of milliseconds behind its
socket), or both, with the starved timer lengthening the first window's recoveries; the part was not printed, so
which cannot be told. The assertions now print it. The caller that receives is held to the same account, and to its
latency window.
### The contribution link record has its own magic, `WLK1`
- **Wire change.** The contribution link record ([extensions/live-contribution.md](extensions/live-contribution.md)
§6.2) begins `WLK1`, no longer `WCL1`. The clip record of live-recording §5.1 had `WCL1` first; the link record,
added later, took the same four bytes, so a reader that tells records apart by their magic could not tell a link
record from a clip record, and the registry listed `WCL1` for the clip record alone. `WCL1` stays the clip
record's. A link record written before this change is `malformed` to a reader of this revision (§6.5 step 2), as a
new one is to a reader of the earlier revision; the signature's domain, `wai:contrib-link` 0x01, and every other
field are unchanged.
- **The registry** lists `WLK1` as a magic of live-contribution §6.2 (REGISTRY.md and `codecs/registry.rs`).
- **The corpus** [`live-conformance/links/`](live-conformance/links/) is regenerated with the new magic, each vector
file now `.wlk1`, and gains `clip_record_magic`: a part that bears the clip record's magic, refused `malformed`.
`live_contribution_verify.py` reads `WLK1`, and requires that a copy of each accepted part given the clip record's
magic is refused. `joule-moq-ingest` writes and reads `WLK1` (`linkrec::MAGIC`).
- **The reverse:** a record with the link record's magic is not a clip record. The live-recording corpus
[`live-conformance/recordings/`](live-conformance/recordings/) gains the clip `link_record_magic`: chunk 4's clip
record beginning `WLK1` and signed under it, refused `malformed` at the clip (live-recording §5.2, which now names
`WLK1` among the other magics). `live_record_verify.py` requires the same refusal of a copy of each accepted clip
given `WLK1`.
- **Breaking:** the corpus's index names the new files, and the files under the old magic are gone:
`record:corpus:wai/live-conformance/links/vectors.json`,
`record:corpus:wai/live-conformance/links/altered_count.wcl1`,
`record:corpus:wai/live-conformance/links/bad_key.wcl1`,
`record:corpus:wai/live-conformance/links/bad_magic.wcl1`,
`record:corpus:wai/live-conformance/links/bad_signature.wcl1`,
`record:corpus:wai/live-conformance/links/bytes_without_delivery.wcl1`,
`record:corpus:wai/live-conformance/links/count_mismatch.wcl1`,
`record:corpus:wai/live-conformance/links/empty_span.wcl1`,
`record:corpus:wai/live-conformance/links/first_part_with_parent.wcl1`,
`record:corpus:wai/live-conformance/links/first_try_over_delivered.wcl1`,
`record:corpus:wai/live-conformance/links/last_byte_two.wcl1`,
`record:corpus:wai/live-conformance/links/last_without_reason.wcl1`,
`record:corpus:wai/live-conformance/links/later_part_without_parent.wcl1`,
`record:corpus:wai/live-conformance/links/margin_past_window.wcl1`,
`record:corpus:wai/live-conformance/links/margin_without_recovery.wcl1`,
`record:corpus:wai/live-conformance/links/mode_unknown.wcl1`,
`record:corpus:wai/live-conformance/links/no_window.wcl1`,
`record:corpus:wai/live-conformance/links/outstanding_not_last.wcl1`,
`record:corpus:wai/live-conformance/links/protocol_unknown.wcl1`,
`record:corpus:wai/live-conformance/links/reason_unknown.wcl1`,
`record:corpus:wai/live-conformance/links/reason_without_last.wcl1`,
`record:corpus:wai/live-conformance/links/recovery_disordered.wcl1`,
`record:corpus:wai/live-conformance/links/recovery_empty_with_figures.wcl1`,
`record:corpus:wai/live-conformance/links/recovery_samples.wcl1`,
`record:corpus:wai/live-conformance/links/requested_below_requests.wcl1`,
`record:corpus:wai/live-conformance/links/rist_main_aes192.wcl1`,
`record:corpus:wai/live-conformance/links/rist_main_psk256.wcl1`,
`record:corpus:wai/live-conformance/links/rist_simple.wcl1`,
`record:corpus:wai/live-conformance/links/rist_simple_encrypted.wcl1`,
`record:corpus:wai/live-conformance/links/rtt_disordered.wcl1`,
`record:corpus:wai/live-conformance/links/rtt_empty_with_figures.wcl1`,
`record:corpus:wai/live-conformance/links/run_empty.wcl1`,
`record:corpus:wai/live-conformance/links/run_outside.wcl1`,
`record:corpus:wai/live-conformance/links/run_overfull.wcl1`,
`record:corpus:wai/live-conformance/links/run_overfull_alone.wcl1`,
`record:corpus:wai/live-conformance/links/runs_overlap.wcl1`,
`record:corpus:wai/live-conformance/links/runs_past_bound.wcl1`,
`record:corpus:wai/live-conformance/links/runs_short.wcl1`,
`record:corpus:wai/live-conformance/links/runs_truncated.wcl1`,
`record:corpus:wai/live-conformance/links/series_head_moved_without_groups.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_head_moved_without_groups.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_cipher.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_cipher.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_grant.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_grant.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_hop.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_hop.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_mode.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_mode.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_principal.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_principal.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_protocol.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_protocol.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_room.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_room.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_session.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_session.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_window.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_other_window.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_part_after_last.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_part_after_last.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_sequence_gap.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_sequence_gap.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_skips_a_part.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_skips_a_part.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_skips_a_part.2.wcl1`,
`record:corpus:wai/live-conformance/links/series_starts_late.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_starts_late.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_three_parts.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_three_parts.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_three_parts.2.wcl1`,
`record:corpus:wai/live-conformance/links/series_time_gap.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_time_gap.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_track_added.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_track_added.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_track_added.2.wcl1`,
`record:corpus:wai/live-conformance/links/series_track_alias_changed.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_track_alias_changed.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_track_dropped.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_track_dropped.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_wrong_parent.0.wcl1`,
`record:corpus:wai/live-conformance/links/series_wrong_parent.1.wcl1`,
`record:corpus:wai/live-conformance/links/series_wrong_parent.2.wcl1`,
`record:corpus:wai/live-conformance/links/small_order_r.wcl1`,
`record:corpus:wai/live-conformance/links/srt_caller_aes192.wcl1`,
`record:corpus:wai/live-conformance/links/srt_cipher_100.wcl1`,
`record:corpus:wai/live-conformance/links/srt_last_outstanding.wcl1`,
`record:corpus:wai/live-conformance/links/srt_listener_clean.wcl1`,
`record:corpus:wai/live-conformance/links/srt_recovered.wcl1`,
`record:corpus:wai/live-conformance/links/too_large.wcl1`,
`record:corpus:wai/live-conformance/links/track_head_without_groups.wcl1`,
`record:corpus:wai/live-conformance/links/track_zero_digest.wcl1`,
`record:corpus:wai/live-conformance/links/tracks_not_begun.wcl1`,
`record:corpus:wai/live-conformance/links/tracks_past_bound.wcl1`,
`record:corpus:wai/live-conformance/links/tracks_unsorted.wcl1`,
`record:corpus:wai/live-conformance/links/trailing_byte.wcl1`,
`record:corpus:wai/live-conformance/links/truncated.wcl1`,
`record:corpus:wai/live-conformance/links/truncated_byte_two.wcl1`,
`record:corpus:wai/live-conformance/links/truncated_not_full.wcl1`,
`record:corpus:wai/live-conformance/links/two_dropped.wcl1`,
`record:corpus:wai/live-conformance/links/unknown_version.wcl1`.
### Who holds which objects near a receiver
- **Holdings** ([extensions/holdings.md](extensions/holdings.md)), the record `wai.delivery.holdings`:
a node's signed record (`WHD1`) of which objects of which groups it holds now, for its neighbours.
Per group, its link hash, its leaf count and either `whole` or a bitmap, in one canonical form,
with the instant it states them at and the one it expires at, signed under SPEC §7 *Signatures*.
Exact, so a neighbour is never sent after an object its holder lacks.
- **What a verifier derives**: a record held to an instant and to the group receipts it has (one
misstated leaf count refuses it whole), a holder's latest record standing and two of one instant
that differ set aside, whether a record holds an object, and an asker's plan: each wanted
object's holders in the asker's order, and the objects no neighbour holds, which come from the
origin. An object a neighbour serves is verified under its receipts like any other.
- **The corpus** [`live-conformance/holdings/`](live-conformance/holdings/): 21 records (a control
for each rule and for the signature, a small-order key among them), 11 verdicts, 6 queries and
a plan over three neighbours. `holdings_verify.py` is a second implementation.
- **The registry** lists `WHD1` as a magic of holdings §4.1 (REGISTRY.md and `codecs/registry.rs`), a magic no
other record, wire identifier, open branch or crate of the repository uses; `holdings::MAGIC` is held to that one
row.
### When each object passes on a bearer with no return path, and when a receiver holds it
- **The carousel schedule** ([extensions/carousel.md](extensions/carousel.md)), the record
`wai.bearer.carousel`: a publisher's signed plan, a jwp-receipts claim of class `schedule`
(§2.4.16) beside the group receipt and the instance's transmission claim, naming for each object
its forward error correction, RaptorQ (RFC 6330 with its erratum 5548) or Compact No-Code
(RFC 5445), with that code's object transmission information, the repair symbols each pass
adds, whether a later pass repeats its symbols or sends new ones, and the times and count of its
passes. Each code's parameters are held to its bounds and the RFCs' division of an object into
source blocks; an entry to its group, its object's content hash and, where the bytes are held,
their length.
- **When an object is held.** For a loss bound in parts per million and an overhead of 0, 1 or 2,
a verifier derives the pass after which a receiver whose losses stay within the bound holds
each object, whatever symbols it loses: a RaptorQ block at K′ + h distinct symbols, the count
RFC 6330 §5.8 states its decoding-failure bound at, and a symbol a repeating pass missed counted
as missed again. It reports the octets each schedule sends and the bound on decoding failure,
and checks a statement that an object or a group is held by an instant. Schedules that
contradict each other, or whose instance is undeclared or two-way, are set aside.
- **The passes a receiver listens to**, from what it holds of each block and when it tunes in: the
passes that add symbols it is certain to keep, when it holds the object, and how long its radio
must be on.
- **Patents** (§11): the IETF IPR disclosures on RaptorQ (#2554 current) and Raptor (#1957
current; #1176 the holder's general statement), each a licence within the holder's standard
terms for a device that implements a wireless wide-area standard and a conditional non-assertion
for one that implements none; no disclosure on Compact No-Code, which is the default a tool that
writes schedules uses. The reference implementation encodes and decodes no symbol.
- **The corpus** [`live-conformance/carousel/`](live-conformance/carousel/): 33 verdicts, 50
claims (every bound of the codes met and passed) and 16 plans. `carousel_verify.py` is a second
implementation; `claim_verify.py carousel` reads every claim through steps 1–4; and
`tests/carousel.rs` checks the table of K′ and Partition[] against an independent RFC 6330
implementation for every block size, and decodes each RaptorQ object a case holds available
from what a receiver within the bound holds.
- **Breaking:** `api:rust:wai::claim_receipt::ClaimClass::Schedule` and
`api:rust:wai::claim_receipt::ClaimBody::Schedule`: the class jwp-receipts §2.4.16 registers, which an
exhaustive match over either needs.
- **Breaking:** the eleven reasons of the class, which an exhaustive match over `Reason` needs:
`api:rust:wai::claim_receipt::Reason::ScheduleNotScheduled`,
`api:rust:wai::claim_receipt::Reason::ScheduleObjectsMalformed`,
`api:rust:wai::claim_receipt::Reason::ScheduleOtiInvalid`,
`api:rust:wai::claim_receipt::Reason::ScheduleSymbolsInvalid`,
`api:rust:wai::claim_receipt::Reason::SchedulePassesInvalid`,
`api:rust:wai::claim_receipt::Reason::ScheduleSubjectMismatch`,
`api:rust:wai::claim_receipt::Reason::ScheduleSignerMismatch`,
`api:rust:wai::claim_receipt::Reason::ScheduleLeafOutOfRange`,
`api:rust:wai::claim_receipt::Reason::ScheduleBeforeGroup`,
`api:rust:wai::claim_receipt::Reason::ScheduleContentMismatch` and
`api:rust:wai::claim_receipt::Reason::ScheduleLengthMismatch`.
### What any file is, from its bytes alone
- **`wai identify <file>`** (operation `media.inspect`, [extensions/media-identify.md](extensions/media-identify.md)): the
formats a file's bytes fit, each with the offsets that say so and how far its structure was followed; a container's
tracks with their codec, size and protection as it states them; the registered capabilities they map to; signatures
found inside it; and, where no format is recognised, measures that still hold: how its values are spread and the lags at
which it repeats. `verdict` is `identified`, `ambiguous`, `signature_only` or `unknown`, and there is no confidence
figure: a candidate's strength says what was checked. Nothing is decoded and no protected track is opened. It reads ISO
media, Matroska, transport streams, Ogg, FLAC, RIFF, MPEG audio, ADTS, PNG, JPEG, GIF, IVF, YUV4MPEG2, FLV, AV1 OBU and
Annex B streams and HLS and DASH text, and knows many more by signature. Any size of file costs a bounded amount, and
every walk has been given hostile bytes. It is in the agent door as `media_inspect`, its results marked
`untrusted_content`, because a file's author chooses the strings in it.
- The corpus [`identify-conformance/`](identify-conformance/) holds files from real muxers and encoders, with what an
independent prober reads of them, and negative controls; `identify_verify.py` is a second implementation.
### Tariff version 6: a job priced per endpoint, a third party's usage billed, and an upscale and a tone mapping billed by what re-derives them
- **Tariff and invoice version 6** ([extensions/receipt-ledger.md](extensions/receipt-ledger.md) §4.9). A job price names
an `endpoint`, or none for every endpoint, since two providers of one capability have different list prices; a price
of any endpoint beside a price of one is refused, since it would bill a job at both.
- **A third party's usage.** A new job unit, `usage`, in a unit the price names. A job source may give `third_party`,
the hop records of [route-record](extensions/route-record.md) §6.2, and a job is billed what its billed hop's record
states the provider bills. A failed hop's billed usage and a record's `charge` reach no line; the line cites the
record, which states both. Records are kind 17.
- **An upscale and a tone mapping** are jobs of their own forms, `media.upscale` and `media.tonemap`, billed in `subpixels`.
An upscale's record is proved by exact-upscale §7 from its input and its prior, and a tone mapping's by exact-tonemap
§8 from its envelope, and what the hop delivered is held to what they prove. Their records are kinds 18 and 19, and a
later invoice may cite them again, as an output receipt is.
- A tariff of versions 1 to 5, its invoices and its sources read, verify and bill as before. The corpus grows from 120
to 169 vectors.
- **Breaking:** `api:rust:wai::receipt_ledger::JobPrice::endpoint` and `api:rust:wai::receipt_ledger::JobPrice::usage`: new
members of a public struct.
- **Breaking:** `api:rust:wai::receipt_ledger::JobFacts::endpoint`, `api:rust:wai::receipt_ledger::JobFacts::usage`,
`api:rust:wai::receipt_ledger::JobFacts::third_party`, `api:rust:wai::receipt_ledger::JobFacts::upscales` and
`api:rust:wai::receipt_ledger::JobFacts::renditions`: new members of a public struct.
- **Breaking:** `api:rust:wai::receipt_ledger::JOB_UNITS`: holds a fourth name, an array of 4.
- **Breaking:** `record:corpus:wai/live-conformance/ledger/vectors.json`: the control `invoice_version` gives an
invoice of version 7, since version 6 is now defined, and is refused `unknown_version` as before; every other vector
keeps its outcome, and the new vectors are added.
### A probe no longer panics on two adjacent start codes
- `wai.video.avs3` and `wai.audio.avs3`: a payload with a start code prefix directly after another (`00 00 01 00 00 01`)
made the unit split slice backwards and panic, so a hostile payload could stop whatever probed it. It is now the empty
unit format-probes §8.2 reads, and the payload is refused. A test now gives every probe 30,000 payloads of hostile bytes.
### A third party's hop has a signed record: what was sent, what came back, and what the provider bills
- **The third-party hop record** ([extensions/route-record.md](extensions/route-record.md) §6.2, §7.2 step 9),
`wai-route-third-party-hop`: a hop a router sends to a third party's endpoint states who it called (`provider`,
`origin`), on whose account (`operator` or `customer`, never the key), the provider's id for the work, each exchange
(method, URL, times, status, and the body sent and received by SHA-256 and length, the response kept where it was
JSON), the outputs by name and content hash, what the provider bills (`usage`) and what it reported charging
(`charge`, its own figure in its own terms, never added to anything). It holds no secret: no header has a member, and
no URL states its query, a provider's signed link being a bearer token; `route_record::third_party::kept_response`
and `url_without_query` make a response and a URL what a record may state. It is signed by the router under its own
domain and named by its digest.
- **It is bound to its hop.** A verification document may have `third_party`, the records' texts, beside its receipts: a
record is held to the route record's signer and job, to its attempt's endpoint, receipt, outcome, error and times, to
that endpoint's candidate's provider and unit, and when it served, its outputs to what the hop's receipt names and
the attempt's count (`third_party_invalid`, `third_party_mismatch`, `third_party_output_mismatch`). No route record
changes: the record names the receipt the route record already names. A document without `third_party` reads exactly
as before, and its outcome has no such member.
- **The corpus.** Routes 126 → 236 vectors: nine accepted (served on the operator's account, with the provider's charge
and job id, a failure the provider bills beside a served hop, one it does not bill, records given out of order, the
customer's account, under a route record of version 2, a response as a record keeps it, and none given) and 101
controls of one defect each, every form and bound of §6.2 among them. `live-conformance/route_record_verify.py`
verifies each from this text, and stops a mutated copy at its first failure, since it only asks whether one fails.
- **Breaking:** `api:rust:wai::route_record::Accepted::third_party`: what the records state, `None` when the document has
none. A struct literal of `Accepted` needs it.
- **Breaking:** `api:rust:wai::route_record::Document::third_party`: the records' texts, `None` when the document has
none. A struct literal of `Document` needs it.
- **Breaking:** `api:rust:wai::route_record::Reason::ThirdPartyInvalid`, `api:rust:wai::route_record::Reason::ThirdPartyMismatch`
and `api:rust:wai::route_record::Reason::ThirdPartyOutputMismatch`: three reasons, which an exhaustive match over `Reason`
needs.
- **Breaking:** `api:rust:wai::route_record::Reason::ALL`: the list of §8's codes holds those three, an array of 25. A
use that names its length needs the new one.
### wai-quantum-desktop 0.1.1: an app on an external drive updates itself, and one on a disk image does not
The macOS app took any path under `/Volumes` to be its disk image, and any other path not to be. An app kept on an
external drive was told to move and did not update, while a disk image mounted elsewhere, or a copy Gatekeeper had
translocated, said it would update itself and could not. It now asks whether the volume it runs from can be written.
- **wai-quantum-desktop 0.1.1:** `update-status` and the updater read the volume's read-only flag, and say why when a
copy cannot update. The disk image is the same as before. It also carries `tauri-plugin-updater` 2.14.1 and `zip` 9,
past RUSTSEC-2026-0429. It is the first version with installers for every platform: the 0.1.0 release has none for
macOS on Apple silicon, because this check stopped that build.
### `c2pa` 0.91
- **`c2pa` 0.90 → 0.91** behind `c2pa_emit`, off by default. It takes `zip` 9, past RUSTSEC-2026-0429 (which `c2pa`
never reached: it does not call `extract`), and `elliptic-curve` 0.14, which leaves no `crypto-bigint` 0.5 in this
workspace. A caller that passes its own `c2pa::Signer` builds against 0.91.
### `wai upscale` and `wai tonemap`: an exact derivation from the command line, with its record and its check
- **`wai upscale run`** (`media.upscale`) upscales an 8-bit RGB PNG under a prior, and with `--record` signs the record
and writes the input's envelope it names. **`wai upscale verify`** (`upscale.verify`) recomputes the record from them
under `require-proof`, and holds the picture to it.
- **`wai tonemap run`** (`media.tonemap`) renders a still's gain map at the headrooms named, and with `--record` signs
the record. **`wai tonemap verify`** (`tonemap.verify`) recomputes it from its envelope, and holds the renditions to it.
- The library functions under them, `exact_upscale::make` and `exact_tonemap::make`, return the record's body for a
caller to sign. `exact_upscale` now also needs `png_still` and `receipt_claims`. The catalogue's verbs gain `upscale`
and `tonemap`.
### wai-quantum 0.8.1, wai-quantum-qpu 0.4.1 and wai-quantum-cli 0.2.4: an optimisation that stops and goes on, a job on a provider that outlives what sent it, and the job service's keys, reruns and tasks
This release carries the six entries on WAI Quantum's packages below it, from a job on a provider that outlives what
sent it down to an optimisation that stops and goes on; the other entries among them are not part of it, and the
desktop app's are its own 0.1.2. Each of the six only adds or fixes, so each package moves its last number
(`wai-quantum/VERSIONING.md`, "Before 1.0"): `wai-quantum-qpu` 0.4.1 requires `wai-quantum` 0.8.1, and
`wai-quantum-cli` 0.2.4 requires both; the job service is 0.2.2, and the Python package is 0.1.2, carrying the 0.8.1
engine. `@transaction-science/wai-quantum` stays 0.1.1, as nothing in it changed, and the desktop app stays 0.1.2.
Nothing is deprecated.
- **wai-quantum 0.8.1:** an optimisation goes on from a checkpoint to the same bytes
(`engine::variational::optimize_resumable`, `call_optimize`, `wai.quantum.optimize-checkpoint/1`); the refusal code
`J034`, for an idempotency key that does not read or that names another request; and `wai.quantum.job/1` lists the
status `interrupted`, which the service has returned since its jobs were first kept across restarts, and gains the
optional `interruptions`.
- **wai-quantum-qpu 0.4.1:** a job on a provider is written down before it is sent (`ledger`), and `job::start`,
`job::check`, `job::follow` and `job::cancel` come back to it from any process. It requires `wai-quantum` 0.8.1.
- **wai-quantum-cli 0.2.4:** `run --provider --no-wait`, `providers jobs | collect | cancel`, and
`optimize --checkpoint | --slice`; `submit` and `task` send each job under a key of their own. The local MCP server
gains `get_provider_job`, `cancel_provider_job` and `list_provider_jobs`, and runs `run_on_provider` as a task.
- **The job service 0.2.2:** `POST /v1/jobs` and `POST /v1/run` take an `Idempotency-Key`, and its MCP server's three
runs an `idempotency_key`; a job it was running when it stopped runs once more (`rerun` in `GET /v1`'s `restored`);
and those three runs are MCP tasks.
- **The Python package 0.1.2:** `Service.submit` and `Service.run` take `idempotency_key`, and a job the service
interrupted ends the wait. Its name is unchanged.
- **What moves with the version:** the engine's version is among the sealed bytes of a stamped result, so the
page's pinned digests move (the course check's 1a9aa0ad… to 49a9fdb8…, the compile's beffb3cb… to 198ea9bd…,
the estimate's db90b147… to acbd7208…), the answer set's stored seal is written again
(`WAI_ANSWER_BUNDLE_WRITE=1`), and the notebooks' stored outputs are stored again. The job pins strip the engine and
schema, and do not move.
- **What a caller may have to change:** nothing.
- Every lockfile that holds one of these crates moves with them (seventeen), so a `--locked` build still reads its
own, and `wai-rs`'s optional requirement on the engine moves to 0.8.1. The views page's manifest records the new
digest of `wai-quantum-views`'s lockfile.
- **Breaking:** `wire:corpus:wai/quantum-conformance/answers/cases/bell-stored.wqa`: the stored answer's receipt
marker seals the engine's version, so it is written again at 0.8.1, as at each release. A conformance run checks a
stored answer with the engine that wrote it: 0.8.0's corpus with 0.8.0, this one with 0.8.1.
- **Breaking:** `wire:corpus:wai/quantum-conformance/answers/cases/bell-stored-changed.wqa`: the same stored answer
with one sample's shots changed, written again with it; it still does not verify.
- **Breaking:** `wire:corpus:wai/quantum-conformance/answers/bundle.json`: the corpus inline, written again with both
stored answers.
### wai-quantum-qpu and wai-quantum-cli: a job on a provider outlives what sent it
A run on a person's provider waited in its process, and a job that had not finished in two hours was cancelled at the
provider, losing its place in the queue. If the process stopped first, the job went on and its id was gone, so its
counts could not be read. A device's queue can take days.
- **wai-quantum-qpu:** `job::start` writes a job down (`ledger`) before it is sent, and the provider's id as soon as it
has one; nothing that cannot be recorded is sent. `job::check` and `job::follow` come back to it from any process, and
seal its counts with what was true when it was sent: the program, the provider, the estimate. Running out of patience
leaves the job where it is. `job::cancel` is someone asking. A request that did not complete may have reached the
provider, so it is never sent again. `job::run` keeps no record, and still cancels what its bound outlasts.
- **wai-quantum-cli:** `run --provider` records the job and says its handle, and `--no-wait` answers with it at once.
`providers jobs`, `providers collect` and `providers cancel` are new: `collect` exits 0 done, 1 ended otherwise, 2 still
at the provider, and 75 when it could not ask, which says nothing of the job. A run that outlasts its two hours now
exits 2, with the job left at the provider, where it was cancelled before. `run_on_provider` answers within a call
with the job running and its id, and `get_provider_job`, `cancel_provider_job` and `list_provider_jobs` are new tools.
- **`wai.quantum.provider/1`:** §5.7 says what a job outlasting its client asks of a provider and a client. Nothing on
the wire changes.
### wai-quantum-cli: a job on a provider is an MCP task
An agent ran a job on a person's provider and then had to remember to ask after it with `get_provider_job`. A client
that speaks MCP 2025-11-25 can now run `run_on_provider` as a task, the protocol's own way to wait.
- **The task is the job.** `initialize` declares `tasks` (list, cancel, `tools/call`) from that version on, and
`run_on_provider` declares `execution.taskSupport` "optional". `tools/call` with `task` records and sends the job as
the call does and answers once the provider has taken it, the job's handle its `taskId`. A run that is not free and
not confirmed makes no task and sends nothing; the error's data carries the plan.
- **Every job recorded here is a task**, whatever sent it: `run --no-wait`, an earlier session, another agent.
`tasks/get` asks the provider once and says why when it could not; `tasks/list` reads the records and asks no one,
since they are one person's own; `tasks/cancel` cancels at the provider and refuses a task that has ended. A task's
time to live is unlimited, as the records are kept.
- **`tasks/result` waits without holding the server.** Asked before the job has ended, it is answered when the job ends,
by a thread that asks the provider at an interval growing from 2 s to a minute, while the server answers every other
message. A `tasks/cancel` here ends that wait at once, and a request the client cancels
(`notifications/cancelled`) is never answered. Nothing is lost if the server stops: the job is in its record.
- A tool call and a task's wait asking after one job no longer both seal it: this process asks one question at a time.
### The route record states what a hop delivered by its count and root, so a hop may deliver any number of objects
- **Route record version 3** ([extensions/route-record.md](extensions/route-record.md) §3.4, §5.2, §6, §6.1), on a
price book of version 2: the attempt that served states `delivered`, the count of the objects it delivered and their
commitment (the root its hop's receipt already names), not their list; a failed attempt, `0` and zeros. Version 2's
list states at most 1024 outputs and grows with them, and a package of a long title holds an object per piece and
rung, thousands; a record of version 3 is the same size whatever was delivered, and nothing a hop signs changes. The
served hop's receipt is held to the root (`receipt_output_mismatch`). A record of version 1 rests on a price book of
version 1, and one of version 2 or 3 on one of version 2 (`pricebook_mismatch`). Records of versions 1 and 2 read and
verify exactly as before.
- **An output's inclusion proof** (§6.1): `index` and `siblings` from the leaf up, held as jwp-receipts §3 step 5
holds an object's: the index below the count, exactly ⌈log₂ count⌉ siblings, the walk ending at the root, so the last
node of an odd level is proved at its own index only. `route_record::output_proof` makes one and
`route_record::verify_output_proof` checks it; `route_record::seal_committed` writes a record of version 3.
- **The receipt ledger** ([extensions/receipt-ledger.md](extensions/receipt-ledger.md) §5.1, §4.8): a job source whose
route record is of version 3 gives each output `proof`, and an output is one its hop delivered when its proof holds
its object to the served attempt's count and root (`output_not_delivered` otherwise; a missing or misshapen proof,
or one under a record of version 1 or 2, `malformed`); a decode of version 5 is held to its places and its count. The
count is the router's statement, which only the objects check; a count stated larger lets one object be proved at a
second place, and no source bills one object twice. Every tariff version that reads job sources reads them.
- **The corpora.** Routes 109 → 126 vectors: six accepted of version 3 (one output, three, 5,000, a failover, a late
hop, receipts not checked) and eleven controls, the unknown-version control now of version 4.
`route_record_verify.py` reads version 3 and has the proof check (`inclusion_proof`); two more checks dropped
(`delivered-form`, `receipt-root`). Ledger 108 → 120 vectors: one accepted (an encode, a decode of version 5 and a
package's run of 1,501 objects, each output proved) and eleven controls; `receipt_ledger_verify.py` checks every proof
with the route verifier's `inclusion_proof`, one more check dropped (`output-proof`), three more mutated copies.
- **Breaking:** `record:corpus:wai/live-conformance/routes/vectors.json`: the control `record_version_3` is now
`record_version_4`, a record of version 4 refused `unknown_version` as before; every other vector keeps its outcome,
and the new vectors are added.
- **Breaking:** `api:rust:wai::route_record::Accepted::delivered`: an accepted record of version 3 states what its
served hop delivered by its count and root, which `Accepted` carries in this new field (`None` for records of
versions 1 and 2, as `outputs` is `None` for version 3). A struct literal or an exhaustive pattern of `Accepted`
outside the crate adds the field, or matches with `..`.
### wai-quantum-desktop 0.1.2: a job on your own hardware, in the installed app
- **wai-quantum-desktop 0.1.2** carries the two entries that follow: a job on one of the person's providers outlives
the window, and the release's Homebrew cask passes `brew style`. A copy of 0.1.1 updates itself to it.
### Quantum Studio: a job on your own hardware outlives the window
A run on one of the person's providers waited inside the app for up to two hours, holding the Studio, and then
cancelled the job, which gave up its place in a device's queue. The app now writes the job down before it sends it,
with the command line's records of jobs on a provider, and asks after it by its id.
- **wai-quantum-desktop:** `run_on_provider` answers within ten seconds, with the job done or still running and its id
(`pj-…`); `provider_job` asks after it once or cancels it, and `provider_jobs` lists the jobs sent, asking no
provider. A job that has ended is answered from its record. Waiting never cancels a job.
- **Quantum Studio:** while a job waits, the status line says where it is and how often it has been asked after,
asking less often the longer it waits; **Cancel** cancels it at the provider, and **Wait in the background** leaves
it there and frees the Studio. A job left waiting, from this window or one closed earlier, is followed while the app
is open, and its result goes to the history when it finishes. **Your jobs** lists every job sent, shows a finished
one and cancels one still waiting. A check that fails while a job waits does not end the wait.
### The desktop release's Homebrew cask passes `brew style`
The cask the release writes had three findings: no blank line between each disk image's checksum and its address,
and no `depends_on :macos`. It now has both, so the tap carries it as the release wrote it with no finding.
### The job service: a job sent again under its key is the same job
A client that lost an answer could not safely ask again: a second submission was a second job, counted against its
limits. The three MCP tools that run also declared `idempotentHint: true`, which was not so.
- **wai-quantum-service:**
- `POST /v1/jobs` and `POST /v1/run` take an `Idempotency-Key`, and `run_quantum_circuit`, `run_quantum_set` and
`run_on_target` take `idempotency_key`. Sent again with the same key and request, a job is answered with the
key's job, before any limit and counting as no submission. With another request, the key is refused (`J034`).
- A key names its job while the job is kept, whatever its status, but for a job the service interrupted: that key
runs the request again. A record keeps the key's digest with the client's name, never the key, and the key names
its job after a restart.
- The three tools' `idempotentHint` is now false: without a key, each call is a new job.
- `GET /v1` states `idempotency`, and the OpenAPI document describes the header.
- **wai-quantum:** the refusal code `J034`.
- **wai-quantum-cli:** `submit` and `task` send each job under a key of their own. Where no answer came back, they
send it again under that key, twice at most, if the service says it keeps keys.
- **wai-quantum for Python:** `Service.submit` and `Service.run` do the same, and take `idempotency_key`. A job the
service interrupted now ends the wait; before, the wait went on to its deadline.
### The job service runs a job it was running when it stopped, once more
A job that was running when the service stopped came back `interrupted`, its request kept. Nothing ran it again unless
its client sent it again.
- **wai-quantum-service:**
- On start, a job that was running is queued again, in its order, to run once more from its stored request. Its
`interruptions` say when the service stopped.
- If the service stops again while the job runs once more, the job comes back `interrupted`, with the reason, and is
not run a third time. A job that stops the service whenever it runs therefore does not run for ever.
- `GET /v1`'s `restored` counts the jobs run again (`rerun`).
- **wai-quantum:** `wai.quantum.job/1` lists `interrupted` among its statuses. The service has returned that status
since its jobs were first kept across restarts, but the schema did not list it. The schema also gains the optional
`interruptions`.
### The job service's runs are MCP tasks
- **wai-quantum-service:** a client that speaks MCP 2025-11-25 may call `run_quantum_circuit`, `run_quantum_set` and
`run_on_target` as tasks. `initialize` declares `tasks` (`cancel`, and `requests.tools.call`), and each run's
`execution.taskSupport` is `optional`.
- **The task is the job.** Its id is the job's id, answered at once as `working`. A program that does not read makes
no task: the answer is an error whose `data` is the refusal, with its codes and verdict.
- **Statuses.** `tasks/get` gives the job as a task: `completed`, `cancelled`, or `failed` for a job that failed or
that the service interrupted. Its `ttl` is the service's retention.
- **The result.** `tasks/result` gives `get_quantum_job`'s answer for the ended job, naming the task in its `_meta`.
Asked early by a client that takes `text/event-stream`, it is an event stream kept open until the job ends.
Any other client is held up to 50 s, then told to poll.
- **Cancelling.** `tasks/cancel` cancels the job, and refuses a task that has ended.
- **Who may read a task.** There is no `tasks/list`: the public service binds a task to no one, and the task's id,
128 random bits, is what reads it. Where the service takes API keys, a task is its key's alone.
### The receipt ledger holds a decode to the frames it delivered, as one animated PNG
- **Tariff and invoice version 5** ([extensions/receipt-ledger.md](extensions/receipt-ledger.md) §4.8): version 4's
layout, rules and prices, and one rule more. A decode (a job whose route record's operation is `media.decode`)
delivers its reconstruction, and the codestream is what it read. Version 4 gives every `int_motion` output as its
receipt and codestream and holds the codestream to what the hop delivered: true of an encode, false of a decode,
whose hop had to state an object it never delivered and commit its receipt to it. Under version 5 a decode's output
also gives `frames`, one animated PNG (PNG's third edition) of 8-bit RGB frames, each the whole picture and blended
over nothing, its default image its first frame, read whole and then frame by frame as packaging §5 reads a still's
image data, each frame's samples hashing (integer-payloads §15.1) to the receipt's frame at its index; the receipt is
still re-derived from the codestream, the decode's input; and the served hop states exactly that animation, by
content hash, and nothing else (`output_invalid` at the animation or the frame, `output_not_delivered` at its place,
or at `outputs` for an object past it). One object, not one per frame, because a route record states a hop's outputs
once each and at most 1024, and a clip runs longer and repeats frames. The frame hash binds the samples, not the
PNG's bytes, so any writer's animation of the right samples holds. An encode, and every other job, is held as in
version 4, and a decode bills its receipts' subpixels as before. A version-4 reader would refuse a decode's frames,
and a version-4 verdict must not change, so this is a version of its own; versions 1 to 4 read, verify and bill
exactly as before.
- **Animated PNG** (`png_still::encode_animation`, `png_still::animation`): a writer every byte of which is a function
of the frames and the delay, and a reader of §4.8's rules that reads the whole structure first and each frame's
samples only when asked, so a long clip is never held decoded.
- **The corpus** adds nineteen vectors: a decode held to its animation beside an encode; a decode whose record states
the codestream it read, and one stating it past the animation; a decode in version 4's form; a frame with one sample
changed, two frames swapped, a still for the animation, a frame missing, frames of another shape holding the same
samples, and a codestream not the receipt's; an animation that blends a frame over the one before, disposes of one by
no defined operation, holds a frame of part of the picture, skips a sequence number, keeps its default image apart
from its frames, counts a frame more than it holds, or has a frame with no data; a decode in version 5's form under
version 4; and a version-4 invoice under a version-5 tariff. `receipt_ledger_verify.py` reads version 5 with an
animation reader of its own, reads each frame's data with the packaging verifier's PNG reader, and drops one more
check (`decode-frames`); three more mutated copies of the corpus must fail. The ledger service (`wai/wai-ledger`)
bills version 5 as version 4.
- **Breaking:** `record:corpus:wai/live-conformance/ledger/vectors.json`: the control `invoice_version` gives an
invoice of version 6, since version 5 is now defined, and is refused `unknown_version` as before; every other vector
keeps its outcome, and the new vectors are added.
### An optimisation stops and goes on to the same bytes
A long optimisation ran to its end in one process or not at all, and one killed partway lost every step.
- **wai-quantum:** `engine::variational::optimize_resumable` (and `call_optimize`, as `call` answers) goes on from a
checkpoint, `wai.quantum.optimize-checkpoint/1`.
- After every step, the caller gets the run as it stands, and may stop it there.
- The checkpoint holds the steps taken with their links and the optimiser's whole state, every number as the bits
of its f64.
- Gone on with from any step, a run ends with the same bytes as one that never stopped, for each optimiser.
- A checkpoint of another request, or one whose steps were changed, is refused (`J052`).
- **wai-quantum-cli:** `optimize --checkpoint FILE` keeps the state in FILE as the run goes, at most once a second and
whenever it stops, written whole or not at all. It goes on from FILE when FILE is there, and removes FILE when the
run ends. A run killed and started again with the same arguments ends with the same bytes, receipt and all.
`--slice N` takes at most N steps, then stops with the state kept (exit 2).
### The receipt ledger bills what the work spends: bytes out, a package's work per run and its storage, every hop's energy, the output its hop delivered
- **A job's output is the object its hop delivered** ([extensions/receipt-ledger.md](extensions/receipt-ledger.md)
§5.1): an output's object is its `codestream`, named by its content hash, and where the job's route record states
what its served attempt delivered (route-record version 2, `outputs`), each output receipt a job source gives is of
one of those objects, once, or the source is refused **`output_not_delivered`**, at `outputs` and its index. A line
bills a job's subpixels from its output receipts, so a receipt of an object the hop did not deliver billed subpixels
nobody delivered for that job, and one given twice billed them twice. The route record binds the hop's receipt to
its outputs (#552); this binds each output receipt to the same outputs. It holds under tariff versions 3 and 4; under
version 4 a record of version 1, which states no outputs, holds its outputs to none, and under version 3 its outputs
are read as before.
- **Routing figures are never charges** (§5.1): a route record's `cost`, `energy`, `expected_cost`,
`expected_ttfb_ms`, and its price book's `price` and `failures`, are the router's figures for choosing an endpoint;
no line reads them, and a job line bills its billed attempt alone, at the tariff's price the buyer signed. Two
controls hold it: a failed hop counted as a job, and a job line at the router's list price, neither recomputes.
- **The room-session receipt, version 2** ([extensions/live-metering.md](extensions/live-metering.md) §4.1, §4.4,
§4.6): a sixteenth unit, `bytes_out`, the payload bytes the hops sent the session's sessions, summed from their
ledgers' `bytes_out` (§3.4), and a fourteenth tariff price, per 10^9 of those bytes. What delivering a session costs
scales with the bytes carried as well as with the time it ran. A grant's consumption entry states its `bytes_out`
(206 bytes), and its funds are ⌈(1000 × *N* + `bytes_out` × the price) / 10^15⌉ minor units, the time and the bytes
rounded up once together. The ledgers count exactly the sessions under a grant or none, so the grants' bytes out sum
to the receipt's; a hop link's bytes, which no ledger holds, are carriage between relays and are not counted. A
receipt of version 1 reads, verifies and prices exactly as before; a version-1 reader cannot read version 2.
- **Tariff version 4** (receipt-ledger §4.7; `WLT1`, `WLI1` with version byte 4) prices three more things by what
spending on them scales with. A **room's bytes out**: a fourteenth room price, a room line of unit `bytes_out` from a
receipt of version 2, and a receipt that states a tariff states all fourteen prices (one of version 1 states no
price of bytes, read as 0, so its funds conflict with a tariff that prices bytes). A **package's work, per run**:
packaging is deterministic, so a package made again (a customer asking for the work done and measured again) gives
the same receipt from a run that did the work again, and every version before billed a package once per receipt and
refused it. In version 4 a package has a job's shape: the run that made it, a routed job of `package.build` whose
output is the package (its receipt's text the object its hop delivered, verified against its indexes), is the work,
billed once per run at the tariff's job prices per job and per **work unit**, a new job unit, the package receipt's
`work_units` (packaging §9.1), the work its figure is expressed against; the package receipt is the product, cited
by each run and billed once by none; a run replayed under a new route record is refused (`record_rebilled`,
`record_duplicate`). The run's energy is its hop's figure, and the receipt's own figure, the same work's, is not
stated again (scope `packaging` is empty in version 4). A **package's storage by byte-time**: the tariff's one package
price is per 10^9 byte-days; a `package` source is the product held and bills its storage alone: one the previous
invoice lists is held through the invoice's whole period, written by its start, and is billed ⌊Σ `stored_bytes` ×
(`end` − `start`) / 86 400 000 000⌋ byte-days and its receipt listed again; the invoice that first lists a package
bills it no storage, since no record shows when in that period it was written. The period is read from the chain's
own invoices, which the seller signs and the buyer holds. The invoice states a sixth unmetered count, so a version-3
reader cannot read it.
- **Every hop's energy** (§6.6): under version 4 an invoice states the energy of every attempt of its jobs, the ones
not billed (a failed hop, a hop that served past the deadline) in a scope of their own, `unbilled_hops`, beside the
billed hops' and never summed with them; such a hop's figure that no row can state is `job_figure_invalid`. A failed
hop spent energy and delivered nothing; before, no row stated it, so the jobs' energy was less than their hops
spent. No line reads the scope: the buyer is still not billed for a failed hop.
- **Jobs routed under the route record's version 2 in the ledger corpus**: an encode, a failover whose failed hop was
metered, a decode, a hop past the deadline, three hops that failed and a failover whose failed hop's figure states no
class, routed by the route corpus's router under its price book of version 2, per operation with measured failures,
each served hop stating its outputs; and two runs of `package.build` making one package, under a price book of
version 2 of their own, and a replay of the first. `routes/vectors.json` is unchanged (`route_record::corpus` adds
the helpers that route them for the ledger).
- **The corpora.** `live-conformance/metering/vectors.json` adds five vectors (a receipt of version 2 with its bytes
priced; bytes out restated, bytes left out of the consumption, bytes priced with no tariff, a receipt of version 3)
and keeps every other byte for byte; `live_metering_verify.py` reads version 2 and requires its new mutated copies to
fail. `live-conformance/ledger/vectors.json` adds twenty vectors (version-2-routed jobs under version 3 and
their two controls; version 4's chain of two invoices and its controls); `receipt_ledger_verify.py` reads version 4,
holds outputs to their content hashes with `claim-conformance/_blake3.py`, requires the new mutated copies to fail,
and drops seven more of its checks (`output-delivered`, `unbilled-hops`, `storage-held`, `package-again`,
`run-work`, `bytes-out`, `conflict-bytes`). The ledger service (`wai/wai-ledger`) reads a source by its tariff's
version and, under version 4, bills each package the last invoice holds again for its storage, and a package made
again by a new run.
- **Breaking:** `api:rust:wai::live_metering::SessionReceipt::version`: a receipt states its version, 1 or 2. A struct
literal adds `version: live_metering::RECORD_VERSION`.
- **Breaking:** `api:rust:wai::live_metering::Units::bytes_out`: version 2's bytes out, 0 in version 1.
- **Breaking:** `api:rust:wai::live_metering::Tariff::bytes`: version 2's price per 10^9 bytes out, 0 in version 1.
- **Breaking:** `api:rust:wai::live_metering::Consumption::version`: an entry states its receipt's version.
- **Breaking:** `api:rust:wai::live_metering::Consumption::bytes_out`: version 2's bytes out of a grant.
- **Breaking:** `api:rust:wai::live_metering::Consumption::encode`: returns a `Vec<u8>`, 198 bytes in version 1 and
206 in version 2.
- **Breaking:** `api:rust:wai::live_metering::Computed::version`: the version of the receipt it was computed for.
- **Breaking:** `api:rust:wai::live_metering::SessionPlan::version`: the version of the receipt a plan aggregates into.
- **Breaking:** `api:rust:wai::receipt_ledger::ROOM_PRICES`: 14, a tariff's room prices with version 4's price of bytes
out; `ROOM_PRICES_1` is the 13 of versions 1 to 3.
- **Breaking:** `api:rust:wai::receipt_ledger::Tariff::room`: fourteen prices, the fourteenth written in version 4 and
read as 0 in versions 1 to 3.
- **Breaking:** `api:rust:wai::receipt_ledger::Invoice::unmetered`: six counts, scope 6 (`unbilled_hops`) last; an
invoice writes and reads its version's.
- **Breaking:** `api:rust:wai::receipt_ledger::Computed::unmetered`: the same six.
- **Breaking:** `api:rust:wai::receipt_ledger::JobFacts::packages`: under version 4, a job's output packages, each its
receipt hash and work units.
- **Breaking:** `api:rust:wai::receipt_ledger::JOB_UNITS`: three names, `work_units` the third (version 4).
- **Breaking:** `api:rust:wai::receipt_ledger::Tariff::package`: in version 4 only the third, per 10^9 byte-days, is
written and read; the first two are 0.
- **Breaking:** `api:rust:wai::receipt_ledger::PackageFacts::held_us`: under version 4, the period a package is held
through, `None` when first billed.
- **Breaking:** `api:rust:wai::receipt_ledger::JobFacts::unbilled`: under version 4, the figures of a job's attempts
that are not billed.
- **Breaking:** `api:rust:wai::receipt_ledger::Reason::OutputNotDelivered`: a job's output receipt of an object its
served hop did not deliver, or given twice (§5.1); a match over the reasons adds it.
- **Breaking:** `api:rust:wai::receipt_ledger::Reason::ALL`: the codes of §10, `output_not_delivered` among them.
- **Breaking:** `record:corpus:wai/live-conformance/ledger/vectors.json`: the control `invoice_version` gives an
invoice of version 5, since version 4 is now defined, and is refused `unknown_version` as before; every other vector
keeps its outcome, and the new vectors are added.
### Quantum Studio: a run on the job service whose answer is lost is sent again, safely
- **Quantum Studio:** a run on the job service goes under an `Idempotency-Key` of its own, a random UUID, to a
service whose description says it keeps keys. Where no answer comes back, the run is sent again under that key,
twice at most, so a job the service took is answered rather than taken twice. A service that keeps no keys is sent
no key, which it would refuse across origins, and no job twice. A poll whose answer is lost no longer ends the
wait; five in a row do.
### The receipt ledger prices media jobs per operation, and bills the work done
- **Tariff version 3** ([extensions/receipt-ledger.md](extensions/receipt-ledger.md) §4.6): each job price
also names an `operation` of the catalogue (`media.encode`, `media.decode`), and a job is priced by its serving
capability **and** its operation, the one its route record names. Within one capability an encode (a motion search,
then a codestream written) and a decode (a reconstruction) spend different energy for the same output units, so one
price per capability overpriced one or underpriced the other, and a decode could not be billed at its own price. A
job whose capability and operation the tariff does not price bills nothing. A version-2 reader cannot read a price
that names an operation (`op_len` and `operation` follow `capability`), so this is a version of its own: a tariff and
its invoices of version 3 (`WLT1`, `WLI1` with version byte 3, the invoice laid out as version 2's).
- **A chain bills a job's work, not its output's identity** (§5.2, §5.3, §6.9, §12): under a tariff of version 3
the records billed once for a job are its route record and its hops' OER/2 receipts, new on every run; its output's
decode receipt (kind 15) is cited, and carried in its lines' citation digests, but is not billed once. The codecs
are deterministic, so the same request run again delivers the same output receipt from a new hop with new energy:
that re-run is billed, in the invoice that holds it or a later one, and its output receipt is cited again. A replayed
hop receipt or route record is refused as before (`record_rebilled` across the chain, `record_duplicate` within an
invoice), so two lines of one invoice cite one output receipt only under different hop receipts; two route records
of one job are two runs of it, no longer `record_overlap`. A tariff of version 2 reads, verifies and bills exactly as
before, an output once; a package keeps its own rule in every version (a delivered product, once per receipt).
- **The corpus** adds nine vectors: encode and decode of `int_motion` at their own prices, a re-run billed in the
next invoice of its chain, one output under two hops of one invoice billed for each, an operation the tariff does not
price, a replayed hop receipt refused, one hop under two lines refused, prices out of order by operation, a version-2
invoice under a version-3 tariff, and the re-run refused under version 2. Its version-3 jobs are routed by the route
corpus's router under that corpus's seeds; `routes/vectors.json` is unchanged. `receipt_ledger_verify.py` reads
version 3 and its rules, requires the new mutated copies to fail, and drops two more of its checks (`operation`,
`output-once`); the ledger service (`wai/wai-ledger`) applies the chain rule of its tariff's version.
- **Fixed:** a job price whose capability (or, in version 3, operation) is not UTF-8 text is refused
`tariff_invalid`, as §4.3 reads it: its form is a rule, not the layout. The reference read it `malformed`, unlike the
text and `receipt_ledger_verify.py`; no vector held such a price.
- **Breaking:** `api:rust:wai::receipt_ledger::JobPrice::operation`: a job price states its operation, empty in
version 2. A struct literal adds `operation: String::new()`.
- **Breaking:** `api:rust:wai::receipt_ledger::JobFacts::operation`: a job source's operation, its route record's.
- **Breaking:** `api:rust:wai::receipt_ledger::check_across`: takes the tariff's version first, which decides what a
chain bills once and whether two records of one job overlap.
- **Breaking:** `record:corpus:wai/live-conformance/ledger/vectors.json`: the control `invoice_version` gives an
invoice of version 4, since version 3 is now defined, and is refused `unknown_version` as before; every other vector
keeps its outcome, and the version-3 vectors are added.
### Media jobs in the receipt ledger, and the receipts of `wai.video.int_motion` written down
- **The receipt ledger prices media jobs** ([extensions/receipt-ledger.md](extensions/receipt-ledger.md) §4.5): a
tariff and its invoices of **version 2** (`WLT1`, `WLI1` with version byte 2) carry job prices, each a capability, a
unit (`jobs`, or `subpixels`: the `work_pixel_ops` of the job's output receipt), a divisor and a price, and bill a
**job source**: a route record's verification document (`wai.route.record`) with its hops' OER/2 receipts and
WAI's decode receipt of its output with its codestream. A job is billed at its serving capability's prices from its
billed hop alone, so a failed hop, a hop past the declared deadline and a job no hop served bill nothing; its energy
is the serving hop's figure in the new scope `jobs`, by class, the unmetered marker counted apart. New record kinds
(14 an OER/2 receipt, 15 an `int_motion` decode receipt, 16 a route record), a scope kind for a router's key, a
line source kind 5, and two reasons, `output_invalid` and `job_figure_invalid`; one hop and one output belong to one
job (`record_duplicate`), and two records of one router for one job are `record_overlap`. A tariff and an invoice
of version 1 are read, verified and billed exactly as before, and every existing vector keeps its outcome but one:
the control that refused an invoice of version 2 as `unknown_version` now gives version 3.
- **One registry of record kinds** (receipt-ledger §5.2): the table now holds every kind a WAI text names a record
by, revenue-share §7.4's run (10) and invoice (11) and opportunity-report §7.3's cue event (12) and room policy (13)
among them, each pointing to the text that defines it, unchanged; those texts say their kinds are registered there.
Kinds **128 to 255 are reserved for private and provisional use**, so an implementation's own records never collide
with a kind WAI assigns later: a WAI record never states one, and an invoice that lists one is refused
`private_kind` (in either version; such an invoice was refused `invoice_invalid` before). An invoice of version 2
lists kinds 1 to 9 and 14 to 16, never another text's.
- **The `int_motion` receipts** ([extensions/integer-payloads.md](extensions/integer-payloads.md) §15): the decode
receipt and its JSON form, which only the code defined, are written down unchanged, and an **encode receipt**
(`wai.video.int_motion/encode-receipt`) is added: the source frames' hashes, the encoder's parameters, the SSE the
payload decodes with, the work and the encode's energy and class; `VideoEncodeReceipt` seals it over the reference
encoder's payload and re-derives it from the payload and the source. `live-conformance/_int_motion_receipt.py`
reads and checks a decode receipt from the text, for the ledger's second implementation.
- **`oer::micro_joules`**: an OER/2 figure as whole microjoules, exactly, a half to the even one.
- **The route corpus** binds each hop's receipt to its job and gives the jobs the ledger bills distinct ids; two
accepted vectors are added (a hop that served unmetered, and one whose figure states no class).
- **Breaking:** `api:rust:wai::receipt_ledger::Tariff::version`: a tariff states its version. A struct literal adds
`version: receipt_ledger::VERSION` and `job: vec![]`, and reads and writes as before.
- **Breaking:** `api:rust:wai::receipt_ledger::Tariff::job`: the job prices of a tariff of version 2; empty in version 1.
- **Breaking:** `api:rust:wai::receipt_ledger::Invoice::version`: an invoice states its version, its tariff's.
- **Breaking:** `api:rust:wai::receipt_ledger::Invoice::unmetered`: five counts, scope 5 (`jobs`) last; a version-1
invoice writes and reads the first four, its fifth 0.
- **Breaking:** `api:rust:wai::receipt_ledger::Computed::unmetered`: the same five.
- **Breaking:** `api:rust:wai::receipt_ledger::Facts::Job`: the facts of a job source; a match over the sources'
facts adds the variant.
- **Breaking:** `api:rust:wai::receipt_ledger::Reason::OutputInvalid`: a job's output receipt that does not read or
does not re-derive from its codestream (§5.1); a match over the reasons adds it.
- **Breaking:** `api:rust:wai::receipt_ledger::Reason::JobFigureInvalid`: a job's serving hop whose figure no row can
state (§5.1); a match over the reasons adds it.
- **Breaking:** `api:rust:wai::receipt_ledger::Reason::PrivateKind`: an invoice listing a record of a kind reserved
for private use (§5.2); a match over the reasons adds it.
- **Breaking:** `api:rust:wai::receipt_ledger::Reason::ALL`: the codes of §10, the three above among them.
- **Breaking:** `record:corpus:wai/live-conformance/ledger/vectors.json`: the control `invoice_version` gives an
invoice of version 3, since version 2 is now defined, and is refused `unknown_version` as before; every other
vector keeps its outcome, and the version-2 vectors are added.
### The route record prices per operation, ranks by measured failure, and binds a hop to its output
- **Version 2 of the route record's price book and record** ([extensions/route-record.md](extensions/route-record.md)
§3.4): a price book that states `version` 2 prices each **operation** of an endpoint in its own entry, ordered by
endpoint and then operation, and a job's candidates are the entries of its own operation. Within one capability an
encode and a decode spend different energy and are priced differently, so one entry per endpoint misstated one of
them to the estimates, the unit-price limit and the ranking. A record's version is its price book's
(`pricebook_mismatch` at `record`, `version` otherwise). Documents and records of version 1 read, route and verify
exactly as before, and a reader of version 1 refuses version 2 rather than misreading it.
- **Measured failure counts in the ranking** (§3.2.1, §4.2, §4.4): an entry of version 2 states its `failures`, the
hops of its operation sent in a period, how many failed, what the provider billed them per unit and how long they
took, or `null`. A job sent to an endpoint until it is served meets `failed / (hops − failed)` failures on average,
each billed to the router and costing the job its time, so a candidate carries its **expected cost** and **expected
time to first byte per successful job**, which the `price`, `ttfb` and `targets` keys read before the list figures:
ordering by them minimises the expected spend over the attempts (an exchange argument, §4.4). A basis under 100 hops
is too thin (the 95% interval of a rate near one half is wider than ±0.1 below 97), and thin or `null` failures are
unknown: the expected figures are `null` and rank after every measured one, by the existing rule for `null`, never
as no failures. The record restates every basis and expected figure. The job never pays for failures: the
unit-price and spend limits read the list price and cost, and a line bills the billed attempt. This is the failure
cost a product of WAI's maintainers [name removed 2026-11-03] proposed, with its physics and its basis, except that
an unknown failure is not counted as zero.
- **A hop receipt is bound to its output** (§5.2, §6): in version 2 the attempt that served states `outputs`, the
content hashes (jwp-receipts §2.1) of what it delivered in order, and its OER/2 receipt's `subject.output` must be
their commitment, or the record is refused **`receipt_output_mismatch`**: a receipt cannot be moved to vouch for an
output it did not produce. [extensions/oer-receipts.md](extensions/oer-receipts.md) §3 now defines a `job`
receipt's `subject.output`: one object's content hash, or for several the jwp-receipts §2.2 Merkle root over their
content hashes in order, unlabelled leaves, the objects distinct; jwp-receipts §2.4.5 points to it. No OER/2
reason or vector changes.
- **The corpus** (`live-conformance/routes/vectors.json`) keeps every vector of version 1 byte for byte but one, and
adds 36 of version 2: ten accepted (the expected-cost ranking with a thin, a failed and an unmeasured basis, a decode
at its own price, the expected time to first byte as sort and as target, the ceiling on the list cost, a book with
no failure measured, a failover, three outputs, a late hop) and a control for each new rule. `route_record_verify.py`
reads version 2 from the text, computes the commitment with `claim-conformance/_blake3.py`, requires the new mutated
copies to fail and drops eleven more of its checks. `wai::route_record::seal_delivered` writes a record of its
price book's version; `seal` is unchanged.
- **Breaking:** `record:corpus:wai/live-conformance/routes/vectors.json`: the control `record_version_2` is now
`record_version_3`, a record of version 3, since version 2 is defined, and is refused `unknown_version` as before;
every other vector of version 1 is unchanged, and the version-2 vectors are added after them.
- **Breaking:** `api:rust:wai::route_record::Entry::operation`: the operation an entry prices, `None` in version 1. A
struct literal adds `operation: None` and `failures: None`.
- **Breaking:** `api:rust:wai::route_record::Entry::failures`: the entry's measured failures (version 2), or `None`.
- **Breaking:** `api:rust:wai::route_record::PriceBook::version`: a price book states its version, 1 or 2.
- **Breaking:** `api:rust:wai::route_record::Candidate::expected_cost`: the expected cost per successful job
(version 2), `None` in version 1. A struct literal adds `expected_cost: None, expected_ttfb_ms: None`.
- **Breaking:** `api:rust:wai::route_record::Candidate::expected_ttfb_ms`: the expected time to first byte per
successful job (version 2), `None` in version 1.
- **Breaking:** `api:rust:wai::route_record::Accepted::outputs`: the served hop's outputs in a record of version 2,
`None` in version 1.
- **Breaking:** `api:rust:wai::route_record::Reason::ReceiptOutputMismatch`: a served hop's receipt that does not name
the outputs its attempt states (§6); a match over the reasons adds it.
- **Breaking:** `api:rust:wai::route_record::Reason::ALL`: the codes of §8, `receipt_output_mismatch` among them.
### Routed media jobs: the route record, OER/2 minor version 1, and the operations a job names
- **`wai.route.record`** ([extensions/route-record.md](extensions/route-record.md)): what a router considered for
one media job and why it chose. The job's request (an operation of the catalogue, a capability and a fallback, its
quantities, hard limits and soft preferences), the price book and a named, versioned routing policy are documents
pinned by SHA-256 over their RFC 8785 forms; from them alone come the candidates, every limit that removes one (a
value the price book does not know counted as the most restrictive), the ranking, the order and the decision, which
the router signs with every attempt, its hop's OER/2 job receipt and whether it was billed. The attempts follow the
order, only the last serves, and a failed hop or one that served past the declared deadline is not billed. Feature
`route_record` (`route`, `seal`, `verify`); the corpus `live-conformance/routes/`, written by
`wai_route_record_vectors`, each control refused at the reason and place it declares; its second implementation
`live-conformance/route_record_verify.py`, which verifies the hop receipts with the OER/2 reference verifier. The
document kinds `wai-route-request`, `wai-route-pricebook`, `wai-route-policy` and `wai-route-record` are wire
identifiers.
- **OER/2 minor version 1** ([extensions/oer-receipts.md](extensions/oer-receipts.md) §3.1): a receipt whose `oer` is
`"2.1"` may state its figure as the unmetered marker, `energy.joules_total` `null`, which a verifier reports as
unmetered and never as a figure, or beside its acquisition class, `energy.class`, which must support exactly the
receipt's grade. Three reasons (`unmetered_invalid`, `class_invalid`, `class_grade_mismatch`), a third canonical
fixture, and `oer-conformance/job_vectors.json` from the hand-declared `generate_job_vectors.py`. A receipt of minor
version 0 reads as before: every existing vector keeps its reasons. A `job` receipt names its operation under
`oer.job.*`, and a route record holds each hop's receipt to that name. The differential fuzzer mutates job receipts
of minor version 1 too.
- **The catalogue**: `route.verify` (`wai route verify <document>`, a build with `route_record`); `package.build` and
`package.verify` (`wai package build|verify`), which run the reference packager `wai-pack` as itself (found with
`--packer`, `$WAI_PACK_BIN`, beside `wai` or on `PATH`), so a job can name its packaging by an operation of the
catalogue. `wai/catalogue/ops.json` is regenerated.
- **`VideoReceipt::from_json`**: the inverse of `to_json`, so a holder of a `wai.video.int_motion` receipt's JSON runs
`verify` and `verify_reconstruction_wiv1` with WAI's own code. `to_json` writes the signer id as an escaped JSON
string, which changes nothing for an id that needs no escape.
- **`wai-pack`'s meter slot is `Send`**: `PackOpts::meter` is `Option<Box<dyn Meter + Send>>`, so a host that packs on
a worker thread hands the packer its meter and the package receipt states the figure.
- **Breaking:** `api:rust:wai::energy_meter::CounterMeter::new`: its read and clock closures are `Send`, so the meter
can cross threads. A caller whose closure holds an `Rc` or a `RefCell` moves its state into the closure or holds it
in an `Arc<Mutex<_>>`.
- **Breaking:** `api:rust:wai::energy_meter::CounterMeter::calibrated_instrument`: the same, for a calibrated
instrument's read and clock.
- **Breaking:** `api:rust:wai::energy_meter::ModelMeter::new`: the same, for the model meter's clock.
### The opportunity report and the statement check: what a channel's records prove, beside what a platform reported
- **`wai.delivery.opportunity`** ([`extensions/opportunity-report.md`](extensions/opportunity-report.md)), a new
record. An **opportunity report**, `WOR1`, states for one party of a revenue-share contract and one period the ad
breaks that aired in the programmes of the splits it shares in: the breaks a room the contract names makes of its
signed cues (`wai.live.ads` §4.3), each cue's SCTE 35 section read for its id, duration and slots (a placement
opportunity's `sub_segments_expected`, or the room policy's slots) and held to the cue's summary, and the ad entries
of the channel's signed program-as-run that no cue covers; each placed at its start on the window that aired it and
attributed to the programme on air by the contract's map and rule. Each line, per split and class (cued,
scheduled), states the breaks, their seconds, their slots and those with none, with a citation digest of the cues,
policies and runs it came from. The report is signed by its issuer, and every figure recomputes from the records.
- **A reported statement**: a platform's figures for the period, per day or per programme, in a documented JSON or
CSV (RFC 4180) form; read, labelled reported, and never evidence.
- **A statement check**, `WCK1`: per period, day and programme, the opportunities, the reported impressions and
revenue, the implied fill (impressions per slot, in millionths) and revenue per slot; flags for a figure no break
stands behind and for a line far from the period's own rate at bounds the check states; and, where the operator's
signed revenue-share statement of the same party and period is given, its impressions and revenue (gross, or the
party's part) beside the reported ones.
- **Operations** `opportunity.create` (`wai opportunity create`), `opportunity.verify` (`wai opportunity verify`, a
report's or a check's document) and `statement.check` (`wai statement check`), in a build with the feature
`opportunity_report`, under the agent rules.
- **Conformance**: `live-conformance/opportunity/`, written by `wai_opportunity_vectors` from fixed seeds: a channel's
day of two owners, a room cueing breaks with every kind of section the text reads, the operator's signed
statements of the day, the reports of both owners and the operator, checks in JSON and CSV (one under-reporting an
owner's programme), and a refused vector for each reason. Its second implementation,
`live-conformance/opportunity_report_verify.py`, verifies rooms with `live_ads_verify.py` and contracts, runs and
statements with `revenue_share_verify.py`, and recomputes every break, line and flag itself.
- `revenue_share`'s run reader and asset rule are shared with the new module (`pub(crate)`); no record's bytes change.
### The revenue share: what each owner of a channel is owed, recomputable from the public records
- **`wai.delivery.revenue_share`** ([`extensions/revenue-share.md`](extensions/revenue-share.md)), a new record. An
operator and every owner it names sign a **share contract**, `WRC1`: its period and currency, its channel and the key
that signs the channel's program-as-run, its carriers (the rooms of `wai.live.ads` and the stitchers of
`wai.video.stitched_presentation` whose evidence is the channel's), each asset's split of integer millionths among
owners and the operator, and the rule for a break between programmes (preceding, following, or a between split). A
contract is in force only when every owner has countersigned it. The operator signs a **statement**, `WRS1`, for one
party and one period: it rests on invoices of `wai.delivery.ledger`, each re-verified by the ledger, and on the
channel's program-as-run windows; each impression the invoices bill from the contract's carriers is placed at its
instant (a break's start, or a stitched marker's `START-DATE`) on the window that aired it and attributed to the
programme on air; each line, per split and class (presented, beacons, unplaced), states the impressions, the revenue
their invoices' tariffs priced them at, and the party's part by the largest-remainder rule, so that the parts of a
split sum to its revenue exactly, with a citation digest of its records.
- **`wai.video.channel` receipts gain a JSON carriage** (revenue-share §4), `ChannelReceipt::to_json` and `from_json`,
which write out the receipt's signed bytes and identity for a verifier that is not `wai-rs`. The signed bytes and
every existing receipt are unchanged.
- **Operations** `contract.create` (`wai contract create`), `contract.update` (`wai contract accept`, an owner's
countersignature), `statement.create` (`wai statement create`) and `statement.verify` (`wai statement verify`), in a
build with the feature `revenue_share`, under the agent rules (`--json`, the exit codes, `--dry-run`, `--yes` before
a file is replaced).
- **Conformance**: `live-conformance/share/`, written by `wai_revenue_share_vectors` from fixed seeds and the ledger
corpus's sources (a room's break-form receipts under a policy that requires claims and under one that does not, and
three streams stitched by others); accepted statements of two owners and the operator over one month, and under each
rule; a refused vector for each reason. Its second implementation, `live-conformance/revenue_share_verify.py`,
verifies each ledger document with `receipt_ledger_verify.py` and recomputes every attribution, line, division and
citation itself.
### Channel lineup and guide: a linear channel's feeds, signed, and its guide checkable
- **`wai.video.lineup`** (`extensions/channel-lineup.md`): a linear channel's signed lineup,
`WLU1`, of one `wai.video.channel` schedule, bound to it by the schedule root: each programme
asset's title, description, categories, rating, artwork by SHA-256, where it may be shown and its
rights holder, and where each programme of the guide begins.
- **The import**: an XMLTV schedule, Media RSS catalogues (one owner's items alone play back to
back from a start), or a JSON form of both, with the SCTE-35 date ranges of the channel's HLS
Media Playlist as its breaks, into the schedule's receipt, the lineup and a live-events `cue`
for each break (its `cue_id` a `splice_insert()`'s `splice_event_id`). Bounded reading, an XML
subset with one document type declaration that defines nothing, and reasons for a gap, an
overlap, a programme with no stop, a time zone named by letters, a break outside its programme,
a window outside the schedule, an unknown asset and the rest of §10.
- **The guide**: an XMLTV document and a now/next document (RFC 8785), rendered from the receipt
and the lineup and naming both; a verification recomputes it, refuses one that places a
programme the schedule does not or states what the lineup does not, and says of each programme
whether the receipt's program-as-run says it aired.
- **The receipt's JSON carriage** is revenue-share §4.1's, `ChannelReceipt::to_json` and
`from_json`, the one JSON form of a channel receipt: the import writes it and a verification
reads it; `wai.video.channel` receipts keep their signed bytes.
- **Operations** `channel.import`, `channel.guide` and `channel.verify` (`wai channel ...`), with
the agent rules; `quickstart`, the shipped `wai`'s build, holds the feature `channel_lineup`.
- **Conformance**: `live-conformance/channels/`, a sample channel of three owners with a control
of each reason, written from fixed seeds by `wai_channel_lineup_vectors`; its second
implementation, `channel_lineup_verify.py`, from the text alone, agrees on every vector, and a
CI step runs both.
### wai-quantum 0.8.0, wai-quantum-qpu 0.4.0 and wai-quantum-cli 0.2.3: QIR and jeff, the variational and algorithm library, pulse and dynamics, the state vector on a fabric's members and resident on the GPU, and components a host registers
This release carries the six entries below it, down to pulse and dynamics. Two of them change
`engine::schema::FILES`, a public array whose length is part of its type, so the engine moves to its next 0.y,
a major bump before 1.0 (`wai-quantum/VERSIONING.md`), and `wai-quantum-qpu`, whose functions take the engine's
types, moves with it. Everything else in them only adds: `wai-quantum-cli` 0.2.3 requires `wai-quantum` 0.8.0 and
`wai-quantum-qpu` 0.4.0; the job service is 0.2.1, and the Python package and `@transaction-science/wai-quantum`
are 0.1.1, each carrying the 0.8.0 engine. The desktop app stays 0.1.0; its installers are released with its update
key. Nothing is deprecated.
- **wai-quantum 0.8.0:**
- programs in QIR 1.x and 2.x under their profiles, and in jeff 0.3, read and written, each conversion checked by
the equivalence ladder (`engine::formats`, the codes `Q700` to `Q721`);
- the variational and algorithm library: a job's parameters bound, exact gradients, deterministic optimisers, the
variational eigensolver, QAOA and algorithm builders (`engine::variational`, `engine::algorithms`, the schema
`wai.quantum.algorithms/1`, the codes `J050` to `J053`);
- pulse and dynamics: OpenPulse read, a pulse simulator, the method `pulse`, schedules and checks, and receipts
that bind the calibrations (`qasm::pulse`, `quantum_pulse`, `engine::pulse`, `schemas/pulse.v1.json`, the codes
`J040` and `J041`);
- the state vector on a fabric's members, and split across several devices on one node (`engine::fabric`, a job's
`fabric`), and the method `stabilizer_rank` for Clifford+T;
- the state vector resident on the GPU, `auto` placed by committed measurements (`engine::fabric_measured`), and
sparse Pauli dynamics on a GPU with the CPU's bits (`quantum_spd::device`, the `pauli` method on the fabric);
- methods, compiler passes, noise channels and file formats behind four traits, every built-in registered
through them, and a host's own components checked and marked uncertified in what they produce
(`engine::registry`, the codes `J060` to `J065`).
- **wai-quantum-qpu 0.4.0:** requires `wai-quantum` 0.8.0, and nothing else changes.
- **wai-quantum-cli 0.2.3:** `convert`, `components`, `fabric`, `gradient`, `optimize`, `vqe`, `qaoa`, `build`,
`pulse schedule | check`, and `run --fabric | --param | --method pulse`; every verb reads `.ll`, `.qir` and `.jeff`.
The local MCP server gains `convert_program`, `list_components`, `list_fabric`, `gradient_qasm`, `optimize_qasm`,
`vqe_ground_state`, `qaoa_maxcut`, `build_algorithm`, `pulse_schedule` and `pulse_check`.
- **The job service 0.2.1:** `POST /v1/convert`, `/v1/gradient`, `/v1/optimize`, `/v1/vqe`, `/v1/qaoa`, `/v1/build`,
`/v1/pulse/schedule` and `/v1/pulse/check`; a job's `source` in another format, its `fabric` and `params`, and the
method `pulse`; its components in `GET /v1`; and its MCP server's tools to match.
- **The Python package 0.1.1 and `@transaction-science/wai-quantum` 0.1.1:** the 0.8.0 engine; their names are
unchanged.
- **What moves with the version:** the engine's version is among the sealed bytes of a stamped result, so the
page's pinned digests move (the course check's 969b2c1c… to 1a9aa0ad…, the compile's ac948133… to beffb3cb…, the
estimate's 8715e76b… to db90b147…), the answer set's stored seal is written again (`WAI_ANSWER_BUNDLE_WRITE=1`),
and the notebooks' stored outputs are stored again. The job pins strip the engine and schema, and do not move.
- **What a caller may have to change:** `engine::schema::FILES`, as the two entries below say; and a caller of
`wai-quantum-qpu`'s `job::run` or `job::compiled_json` passes 0.8.0's types.
- Every lockfile that holds one of these crates moves with them (seventeen), so a `--locked` build still reads its
own, and `wai-rs`'s optional requirement on the engine moves to 0.8.0.
- **Breaking:** `wire:corpus:wai/quantum-conformance/answers/cases/bell-stored.wqa`: the stored answer's receipt
marker seals the engine's version, so it is written again at 0.8.0, as at each release. A conformance run checks a
stored answer with the engine that wrote it: 0.7.0's corpus with 0.7.0, this one with 0.8.0.
- **Breaking:** `wire:corpus:wai/quantum-conformance/answers/cases/bell-stored-changed.wqa`: the same stored answer
with one sample's shots changed, written again with it; it still does not verify.
- **Breaking:** `wire:corpus:wai/quantum-conformance/answers/bundle.json`: the corpus inline, written again with both
stored answers.
### wai-quantum: extension points, registration traits for methods, passes, noise channels and formats
- **`engine::registry`**: four traits a component implements, and one registry the engine goes through. `Method`
(its name, version, accuracy classes and descriptor, whether it models noise, where `auto` tries it, its `plan`
that admits a job or refuses it and estimates its memory and work, and its `run`); `Pass` (the gates it takes and
gives, and its check: `Check::Ladder`, the equivalence ladder, or `Check::Fidelity`, a distance bound on dense
unitaries; never a hash); `Channel` (its parameters and ranges, its arity, its Kraus operators and its Pauli form
where it is one, and how each noisy method takes it); `FrontEnd` (a format read to OpenQASM 3 and written from the
engine's program). `Registry::with_builtins()` holds every built-in, each registered through the same trait: the
twelve methods and the pulse simulator, the eight compile passes, the five noise channels and the three formats.
`register(Component::…)` adds a host's own in its process; nothing is loaded from a file, a path or a request.
- **The engine through the registry.** `catalog::plan`, `catalog::read`, `run::run`, `compile::compile`,
`compile::call`, `formats::convert`, `formats::convert_json`, `formats::expand` and `verdict::of_run` keep their
signatures and run through the built-in registry; `Registry::plan`, `run`, `plan_and_run`, `job`, `compile`,
`compile_call`, `convert_json` and `expand` run through a host's. `auto` tries methods by rank (the built-ins at
10, 20, 30, … in the order they always had), and the methods list is each method's own entry. A compile takes
`passes`, the names of passes to run after its level's and before twirling and decoupling; a job's `noise` takes
a registered channel by its value or its parameters (`{"name": {"p": …}}`), which the density matrix applies as
its Kraus operators after each gate.
- **The engine checks what a registered component gives.** A method's result must carry an accuracy class it
declared and leave the members the engine writes as they were; a pass's output is held to its input on the whole
circuit by the check it declares, within its bound and what is left of the ε budget, or left out (`rejected`) and
the program goes on as it was; a channel's Kraus operators are checked completely positive and trace preserving at
every job's values (`registry::validate`), and its Pauli form a distribution that is its operators' channel; a
format's program must read. The built-in channels and passes are held by tests to what they declare.
- **Identity.** A component's identity is its kind, name, version and the BLAKE3 digest of its descriptor
(`registry::digest_of`). A result made by built-ins alone is the result it always was: the eight pinned job
digests hold through the built-in registry and through one that holds a host's components beside them. A result
a registered component took part in carries `components`, every component that made it (its format, its
compile's passes, its noise's channels, its method), each registered one marked `"origin": "registered"` and
`"certified": false`, sealed by its receipt; so does a compile's answer (and its pass's record, `component`) and a
conversion's. A name a built-in holds is never registered. `components` is in `wai.quantum.job-result/1`,
`wai.quantum.compile-result/1` and the conversion's answer (optional), and `wai.quantum.tools/1` gains the
listing's `components` and `component`.
- **Refusal codes, a decade of their own:** `J060` a pass a request names that this host does not hold, `J061` a
noise channel the method cannot take (a method that models no noise, which was `J011`, and amplitude damping on
the stabilizer's frames among them), `J062` a channel whose operators fail the validity check, `J063` a
registration the registry does not take, `J064` a registered component's answer that breaks the contract, `J065`
an intent on a job whose noise names a registered channel. A method a job names that does not exist is `J010`
whatever else the job asks, and a channel's value that is not a number or an object of its parameters is refused
(`J003`), where it was read as no noise.
- **Surfaces:** `wai-quantum components` (with `--json`, each one's descriptor); the MCP tool `list_components`
on both servers; the job service lists its components in `GET /v1` and `quantum_service_info`, the built-ins
alone.
- **The example** `examples/extension_points.rs`: a method (`basis_table`, for circuits that permute basis states),
a pass (`cancel_pairs`), a channel (`bit_flip`) and a format (`gate_list`), written against the public traits
alone, registered and run end to end, with its tests; `docs/extension-points.md` says how to write one, with a
channel registered and run. CI: `wai-quantum-registry.yml`, on both Linux architectures and under
wasm32-wasip2.
- `engine::fabric_measured`'s test of the measured circuit's job runs where the fabric is built (`quantum_fabric`),
so the contract workflow's feature set passes every test it builds.
### wai-quantum: the state vector resident on the GPU, auto by measurement, and Pauli propagation on the GPU
- **Resident** (`quantum_fabric::sv64`, host layout 2): a member runs a whole plan with the state on the device.
The state is zeroed there (or uploaded once, for a partition), every launch's parameters go up once as a
`LaunchTable`, each launch is enqueued in the CPU's order on one stream or in-order queue with no wait between
them, and the state is read back once (on CUDA through two page-locked buffers in turn). The kernels are the
same formulas; a gate on two to five qubits takes the kernel compiled for its group size (`sv64::KERNELS`,
`sv64::many_kernel`), its group in registers. The host layout is part of every implementation id, so each
member's certificate is a certificate of the resident path. `CudaSv`, `HipSv` and `OpenClSv` gain
`run_from_zero`; `run_launches` keeps its signature. `max_qubits` now follows the device's memory
(`sv64::qubits_in`, at most 32) rather than the dyadic core's 26; 27 on the laptop GPU here.
- **The wider kernels' own certificate** (`sv64::KERNEL_SV_RUN_WIDE`, `sv64::SV_WIDE`, 120 cases at fusion 4 to 6,
pinned as `SV_WIDE_PIN`): the pinned corpus reaches only the kernels of gates on up to three qubits at once
(`sv64::NARROW`), so a member is placed for a job whose gates act on more than three at once only where these
kernels certified bit-exact too; otherwise such a job on it is refused (`J013`), and `accelerator` passes over
it. A ground-truth test caught one CPU's OpenCL runtime reordering the five-qubit kernel's sums on about one input
in six, which a corpus of 36 cases let through; at 120 cases its wider kernels do not certify, and it runs gates
on up to three qubits at once. The receipt's `fabric` carries the member's `wide` certificate when the job used
those kernels, and `wai-quantum fabric` lists each member's (`fabric_wide` in `wai.quantum.job-result/1` and
`wai.quantum.tools/1`, optional).
- **Ground truth first**: the table a member uploads decodes to the launches planned and replays to the CPU's
state, over the pinned corpus and random circuits reaching every kernel; on each device, random circuits at
fusion 0 to 6 (`sv64::random_cases`), from `|0…0⟩` and from a state given as a partition runs, are the CPU's
bit for bit. Placed on the fleet's laptop GPU (by CUDA and by OpenCL), the CPU beside it by OpenCL, and its
integrated GPU (by HIP and by OpenCL), with the split over them, the CPU's answers byte for byte.
- **`auto` by measurement** (`engine::fabric_measured`): whole jobs of one circuit family (ten layers of
arbitrary-angle rotations and brickwork entangling pairs, 1000 shots) timed on the CPU and on each member, five runs
of each alternating, at fusion 3, 2 and 1, from 18 qubits to as many as half the host's memory held, on three hosts
(2026-10-30 and 31); the rows, each host's load and what discovering its members took are committed, and
`examples/fabric_timing.rs` measures them again. A row is faster when every run on the member beat every run on
the CPU. `auto` places a state on a member from the smallest measured size from which every row up to the largest
is faster, up to that largest, at the fusion width measured, for a plan of at least the measured launches
(`fabric::launches`); a process that has not discovered its members yet does so only where the saving also covers
the measured discovery. Hosts are named by `fabric::host` (the processor's model, architecture and threads,
hashed), members by id. Results: the laptop GPU is never faster from 22 qubits up at any fusion width (26 qubits,
fusion 3: 3.74 s on the CPU, 4.44 s by CUDA, 4.29 s by OpenCL), so `auto` stays on its CPU; the integrated GPU
beat its CPU in every run at fusion 3 and 2 from 18 to 28 qubits (22 qubits, fusion 3: 205 ms against 108 ms by
HIP and 102 ms by OpenCL; 28: 9.81 s against 8.47 s and 8.14 s) and lost from 22 qubits at fusion 1, so `auto`
places fused jobs there (from 18 qubits once discovered, at 28 in a fresh process, discovery taking 1.2 s). On
an aarch64 board (14 cores sharing 122 GiB with an integrated GPU of compute capability 11.0, by CUDA and by
OpenCL) the GPU won some rows below 24 qubits and lost every row from 26 qubits at every fusion width, by either
(28 qubits, fusion 3: 22.5 s on the CPU against 27.7 s), so `auto` stays on its CPU.
A member `auto` chose that fails during the run hands the job back to the CPU. The result's `fabric` carries
`auto` (the rule, the host, the day, the load, the discovery and the measured row it rests on), and the plan's
reasons say so; `wai-quantum fabric` prints the rule, where it places here and every measured row (`auto_rule`).
New optional `fabric_auto` and `fabric_spread` in `wai.quantum.job-result/1`, `auto_rule` in
`wai.quantum.tools/1`. A job on any other host, member or fusion width runs as it did, its result unnamed.
- **Sparse Pauli dynamics on a GPU, with the CPU's bits** (`quantum_spd::device`): the sum of terms lives on a
device (`SpdDevice`), which runs each step's arithmetic on every term at once (one multiply by `sin`, one by `cos`,
one addition where a new term meets an old one, sign changes; no fused multiply-add), while the host keeps what
decides the term set: a rotation's new terms found in the table (dead slots included) and the rest appended in the
order of the terms they came from by an order-preserving prefix sum; dead and live terms counted, and the table
compacted, exactly when the CPU does; the magnitudes a truncation dropped handed back in table order and the bound
and the value added on the host in table order (`expectation_on`). Ground truth first: `HostDevice` runs the
protocol on the CPU and is the CPU's propagation bit for bit, final table included, over 100 cases at one thread
and many. The CUDA member (`CudaSpd`, kernels `quantum_fabric::spd`, implementation `cuda-spd`) is certified on
`SPD_DEFAULT` (40 cases on one to four words, term and weight caps, layered circuits whose dropped strings come
back and are revived in place, and an exact cancellation; pinned as `SPD_DEFAULT_PIN`), its output every value,
bound, threshold reached, count and the final table slot for slot, so a device that keeps the numbers but not the
term set or its order is caught (six mutants, each killed). On the laptop GPU: certified, 24 wider random cases up
to two million terms and the 127-qubit kicked-Ising circuit the CPU's result bit for bit.
- **The `pauli` method on the fabric**: a job asks with `fabric` (`accelerator`, or a member's id or key) and its
sum of terms runs on that member, the CPU's answers byte for byte, the result's `fabric` naming the member and its
certificate (kernel `wai.quantum.spd/propagate`); `cpu` names the CPU; a split is refused for it (`J013`); `auto`
keeps it on the CPU, no member being measured faster for it. Its members are discovered apart from the state
vector's, the first time a job asks for one, and `wai-quantum fabric` lists them (`pauli` in
`wai.quantum.tools/1`, optional).
- **CI**: the state-vector legs run four tests (the random circuits added) and check the wider kernels'
certificate; the placement legs run seven (a job of wider gates placed or refused, a `pauli` job on each member)
and hold `auto` to the measurements on each host (the laptop's GPU never placed; the integrated GPU placed at 18
qubits and fusion 2, a shallower job kept on the CPU); the CUDA leg certifies the propagation and kills its
mutants; the container legs name the table's, the corpora's, the rule's and the protocol's tests.
### wai-quantum: simulator scale-out, the state vector on the fabric's members
- **Placement** (`engine::fabric`): a job's new `fabric` field says where the `statevector` method's state runs:
`auto` (the default), `cpu`, `accelerator`, a member's id or key, or a list of members, a power of two of them,
the state split by its top qubits into one partition on each. The CUDA, HIP and OpenCL members' dense f64 state
vectors (`quantum_fabric::sv64`) are discovered once per process, compiled and certified against the CPU on the
pinned corpus; only a member certified bit-exact is placed, so a placed job's answers are the CPU's byte for
byte. **`auto` places on the CPU** on every host until a member is measured faster than the CPU: with one launch
per gate and the state copied to the device and back, a whole job took about as long either way, or longer on the
GPU, between 18 and 22 qubits on the fleet's GPUs. A member runs a job only when the job asks for one:
`accelerator` takes the first certified member that holds the state, GPUs first, and one that fails during the run
hands the job to the next, the receipt listing each that failed (`fell_back_from`). A placement the host cannot
honour is refused with the new code `J013`, never made elsewhere. A job that does not ask has the plan and result
it always had, on every host; the pinned digests are unchanged.
- **The fabric's receipt in the job's**: a placed result, or one whose job asked for a placement, carries `fabric`
(the kernel, the placement asked, the member or each partition's, its certificate's parity tier, digest and
corpus, and the energy, unmetered with the reason), sealed by the job's receipt; its provenance's `computer`
names the members. New optional members of `wai.quantum.job-request/1`, `wai.quantum.job-result/1` and
`wai.quantum.provenance/1`, and `fabric_members` in `wai.quantum.tools/1`.
- **Several devices on one node** (`quantum_fabric::sv64::run_split`): each partition runs the launches the CPU
plans on its own amplitudes, each runner on a thread of its own; a gate on a partition qubit first has that qubit
exchanged with a local one through the host's copy of the state. Held bit for bit to the CPU's state vector over
the pinned corpus at every partition count a case admits and on wide states with exchanges, on CPU partitions in
every container. On the fleet: a laptop GPU by CUDA and by OpenCL, the CPU by OpenCL and the CPU reference,
one state split four ways over them; an integrated GPU by HIP and by OpenCL; and each GPU beside the CPU and split
over itself, each giving the CPU's answers byte for byte. Each member states the most qubits it holds (25 on every GPU
here), and a state or partition past it is refused before anything runs.
- **Members' runs from a given state**: `CudaSv::run_launches`, `HipSv::run_launches` and
`OpenClSv::run_launches`, with `max_qubits`, each refusing a state or a launch outside what it holds.
- **Surfaces**: `wai-quantum run --fabric` (repeatable) and `wai-quantum fabric [--json]`, the command line's
`cuda`, `hip` and `opencl` features; `fabric` in both MCP servers' job tools and `list_fabric` on the command
line's; the job service reads `fabric` and places on its CPU. The generated agent docs carry it.
- **CI**: `tests/fabric_placement.rs` on jetson-hub (CUDA and OpenCL) and framework-128gb (HIP and OpenCL) in the
hardware workflows, each with its declared members and a split over them, and absent where there is no device;
the split and the planner's refusals in the containers.
- **Stabilizer-rank Clifford+T** (`quantum_stabrank`, the method `stabilizer_rank`): a T gate is a sum of two
Clifford gates of equal weight, so a circuit with `t` T and T† gates is a sum of `2^t` stabilizer states, each run
exactly on the phased tableau, whose new `PhasedTableau::amplitudes` gives every amplitude of a stabilizer state
exactly from one reduction. Every term is counted as a whole number (`ExactSum`), so the sum does not depend on
how the branches are split between threads; an expectation value is the amplitude `⟨0|U†PU|0⟩`. The exact mode's
terms grow as `2^t` (`4^t` for an expectation value); the sampled mode (`terms`, or `epsilon`, at `confidence`)
draws branches uniformly and states the bound `sqrt((sec²(π/8)^t − 1) / (terms · (1 − confidence)))` on each
value with its confidence, computed from the T count, the terms and the confidence. `Program::clifford_t` lowers
`t`, `tdg`, `p`, `rz`, `rx` and `ry` at odd multiples of π/4, `cp` at odd multiples of π/2, `ccz` and `ccx` with
the global phase kept exactly. Held to the dense state vector by every amplitude and by fidelity, never by hash.
`auto` takes it for amplitudes or expectation values past the dense state vector when its branches fit one job's
work, so it is chosen by the T count; it reaches the methods list, both MCP servers, the service and the Studio
(its plain name in English and Simplified Chinese, the views page rebuilt in the CI image) by the same name. The result's accuracy, amplitudes and expectation values gain optional `t_count`, `terms`, `bound` and
`confidence` (`wai.quantum.job-result/1`), and the class `estimate` now also covers a bound with the probability it
holds.
### wai-quantum: the variational and algorithm library
- **Parameters in a job** (`engine::catalog::bind_params`): a job's `"params"` gives each of its program's parameters
(`input float theta;`, `input angle phi;`) its value, written into the program as a `const` at exactly that double
before it is read, every line keeping its number. A parameter left without a value, a name the program does not
declare, or a value that is no number is refused with `J050` (and `Q114`, the reader's code for an input given no
value). A sweep may fix some parameters in `"params"` while it sweeps the others. A job without `"params"` runs to
the bytes it gave.
- **The reader carries each gate's derivative** (`qasm::tangents`): every gate's parameters with their derivatives by
each of the program's parameters, forward, through sums, products, quotients, constant powers, the built-in
functions, loops, broadcast and gate definitions. What it cannot follow (a parameter under a modifier, in a
classical variable, a condition, an index, a size, a loop's range or a subroutine, or a type that rounds it) is
refused on its line with the new reader code `Q129`.
- **Exact gradients** (`engine::variational::gradient`): a Hamiltonian's expectation (a sum of Pauli terms in the form
of a job's observables) and its gradient by each parameter, on the double-precision state vector, by adjoint
differentiation or the parameter-shift rule (two terms for a generator with two eigenvalues, four for a controlled
rotation's three). Every answer states its method, that it is f64 and approximate, and an a priori bound on the
rounding of the value and of each component. Each method is held to gradients worked out by hand for every gate
with a parameter within that bound, to central finite differences, and to the other.
- **Deterministic optimisers** (`engine::variational::optimize`): gradient descent with a fixed step, Adam, and SPSA
from a seeded stream, using only `+ − × ÷`, `√` and the crate's own `exp` and `ln`. Every step's parameters and
value are listed, each a link of a BLAKE3 chain from the request (`wai.quantum.trace/1`), and the receipt seals the
program, every step, the method and the seed; `engine::variational::verify` checks the chain and runs the answer
again to the same bytes. The answers' digests are pinned and hold on x86_64 Linux, arm64 macOS and in wasm32.
- **The variational eigensolver and QAOA** (`engine::variational::vqe`, `qaoa`): an ansatz (a program, or the
hardware-efficient ansatz the variational machine-learning module trains, written as a program and held to that
module's distribution and gradient) optimised on a Hamiltonian, its energy set beside the exact ground energy by
diagonalisation up to 8 qubits; Max-Cut built from a graph, its program and cost Hamiltonian, optimised and read
against the maximum cut by brute force. Held to closed-form spectra, to the p = 1 expected cut on an edge and a ring,
and to brute force.
- **Algorithm builders** (`engine::algorithms::build`): phase estimation, amplitude estimation by phase estimation of
the Grover operator built from `A`, the ripple-carry adder in place, and quantum signal processing of a signal or
of a real symmetric matrix through its block encoding. Each answer carries the equivalence ladder's verdict
against a reference built from the definition, not the program's gates: the ladder gains a rung against a
reference matrix (`engine::equiv::against_matrix`) and one against a truth table (`against_permutation`). Held to
the Fejér distribution, a truth table written apart and run bit-exact, and the Chebyshev polynomials.
- **Every surface:** the command line's `gradient`, `optimize`, `vqe`, `qaoa` and `build`, `--param` on `run`, `plan`
and `submit`, and `verify` running these answers again; both MCP servers' `gradient_qasm`, `optimize_qasm`,
`vqe_ground_state`, `qaoa_maxcut` and `build_algorithm`, and `params` on `run_quantum_circuit`; the job service's
`POST /v1/gradient`, `/v1/optimize`, `/v1/vqe`, `/v1/qaoa` and `/v1/build`, within its own bounds (`J031` past
them, naming the local command); the page engine's `QuantumEngine.variational`. Every answer follows the new schema
`wai.quantum.algorithms/1`. The job codes `J050` to `J053` join the table. The reference page is
`wai-quantum/docs/variational.md`.
- **CI:** `wai-quantum-variational.yml` runs every new test by name on x86_64 and arm64 and builds each feature set
the hosts build without warnings; the contract requires the new schema test; `wai-web-wasm.yml`'s pins rise to 27
(native) and 19 (wasm32). The job service's smoke checks now use every submission a client's window holds (30).
- **Breaking:** `api:rust:wai_quantum::engine::schema::FILES`: the committed schemas grow from 23 to 24 with
`algorithms.v1.json`, the variational and algorithm library's answers. Code that names the array's length iterates
over `FILES` instead.
### wai-quantum: QIR under its profiles and jeff, read and written, every conversion checked
- **QIR** (`engine::formats::qir`): a module read as LLVM text, QIR 1.x (typed pointers) and 2.x (opaque pointers),
under the profile its entry point names. Under the base profile a branch, a classical instruction, reading a result,
or a gate or reset after a measurement is refused as what the profile forbids (`Q704`), on its line. Under the
adaptive profile, measurements anywhere and forward branches on results (`__quantum__rt__read_result`, or QIR 1's
`result_equal`) with the `i1` logic the profile allows are read as `if … else`, each branch to the block where its
paths meet; an optional capability the module flags do not declare is forbidden, and one they declare that the reader
does not take (loops, `switch`, `phi`, IR-defined functions, dynamic qubits or results, arrays) is refused as such
(`Q705`). Integer and float computation the flags declare is computed on reading where its operands are constants.
Rotations at any angle are read at their exact doubles, with a note (`Q709`) that the double-precision methods take
them and the bit-exact method does not; a Clifford+T module stays on the bit-exact path. A program is written as QIR
2.0: the base profile, or the adaptive one (forward branches on results with `read_result` and `br` alone) for a
program that measures in the middle, resets or branches; Clifford+T is written as Clifford+T intrinsics and every
angle as its exact hex double. The crate's bit-exact `quantum_qir` is unchanged.
- **jeff** (`engine::formats::jeff`, format 0.3): read and written through a small Cap'n Proto reader and writer of
the crate's own, with no dependency (segments, far pointers, every element size, each pointer bounds-checked, a limit
on the words visited and the depth). The entry point's dataflow graph is run in an order its values allow: registers
extracted, inserted, sliced, split and joined by index, `for` and `while` decided on reading unrolled, a `switch` on a
measured bit read as a condition, calls inlined, classical values computed the same on every platform. Well-known
gates take their controls after their targets, as the schema orders them; a Pauli-product rotation `exp(iθP)` is
lowered exactly; a custom gate is read only by a standard gate's name (`Q711`). The writer gives one function, every
gate well-known and exact with its global phase, the bits a returned array; a program that branches is refused
(`Q720`).
- **Codes** `Q700` to `Q721` in the reader's table (`docs/qasm-diagnostics.md`): every refusal names its place (a
QIR line, a jeff function and operation). `docs/formats.md` says what each reader takes.
- **Every surface:** `wai-quantum convert FILE --to qasm|qir|jeff [--profile] [-o] [--json]`, each conversion checked
by reading the output back and holding it to the program with the equivalence ladder (a program that branches, step
for step or by every outcome's probability); every verb reads `.ll`, `.qir` and `.jeff` files, or `--from`. Both MCP
servers serve `convert_program` (`tools.v1.json#/$defs/convert`); the job service answers `POST /v1/convert`, and a
job may carry `{"source": {"format", "program" | "program_base64"}}` in place of `qasm` (`job-request.v1.json`), as
both MCP servers' run tools take it (`run_quantum_circuit`, `plan_quantum_circuit`, `run_on_target` and the dry run),
one schema and one refusal; a request whose `qasm` is what its `source` reads to is read as it is.
The Studio opens `.ll`, `.qir` and `.jeff` files through `engine_convert` (the desktop app through its `convert`
command): the program goes into the editor as OpenQASM 3, with the format, the check and each note under it, and a
refusal leaves the editor as it was with its code.
- **Hostile input:** every size, count, index and bound a file gives is held to a cap before anything is made from
it (a register's size before its qubits: a jeff register of 2^32 − 1 qubits is refused, `Q111`, where it once asked
for 34 GB and trapped in a page); the wire reader bounds every pointer in 64 bits and counts each list element
against the words it visits; the jeff reader counts every register slot, array element, call frame and condition
term against one budget; the QIR reader bounds its branches' nesting, the instructions it runs over every path and
the state each path copies; a condition is at most 1 024 terms and 32 deep once written. A conversion's check is
not run past 262 144 gates applied or 4·10⁹ amplitudes, and says so. `tests/formats_mutation.rs` mutates every file
of the conformance set from a fixed seed (45 153 inputs: bits flipped, files cut and spliced, every length, count,
offset and 32-bit field inflated) and holds each to converting or a refusal with a code, within 20 s and 1 GiB,
natively and under wasm32-wasip2 in `wai-quantum-formats.yml`.
- **Conformance** (`wai/quantum-conformance/formats/`): the QIR specification's two examples, every intrinsic, a QIR 1
module, each profile's refusals, hand-built jeff modules, the jeff project's own structured benchmarks and examples
unchanged, and OpenQASM written to each format with the QIR bytes pinned, run natively and under wasm32-wasip2
(`wai-quantum-formats.yml`). `wai-web-wasm.yml`'s pins move to 19 (wasm32) and 27 (native).
### wai-quantum: pulse and dynamics, simulated, the same bits everywhere, its receipts binding the calibrations
- **OpenPulse read** (`qasm::pulse`): a pulse program's `cal` and `defcal` blocks on physical qubits run in
order into a schedule on frames: ports (by the names `d0`, `m0`, `a0`, or mapped), frames with times of their
own, the waveform templates `constant`, `gaussian`, `sech`, `gaussian_square`, `drag` and `sine` and explicit
samples, `play`, `delay`, `shift_phase`, `set_phase`, `shift_frequency`, `set_frequency`, captures and
barriers. A gate starts when its qubits and its frames are free, and a frame's carrier runs on across a
frequency change. Durations are whole samples of `dt`. What the reader does not take is refused with a code and
its place: `Q453` to `Q462` join the table after `Q452`, renumbering nothing.
- **The pulse simulator** (`quantum_pulse`, in `quantum_engine` and `full`): two or three levels per qubit in its
rotating frame, the drive its frames' carriers give, anharmonicity, static couplings, and relaxation and
dephasing from T1 and T2, integrated sample by sample in each tone's frame by a twelfth-order series that uses
only `+ − × ÷` and `sqrt`, so the final density matrix is the same bits on x86_64, arm64, wasm32-wasip2 and in
the browser; CI pins its digest on each. The plan states a proved bound on the trace distance from the model's
exact solution, with an estimate of rounding beside it, and refuses a step longer than 1/λ or a schedule whose
error would pass its tolerance, naming the steps that would meet it. The tests hold it to closed forms within
that bound: Rabi turns, a quarter turn of phase, detuned drives, T1 decay and T2 dephasing.
- **Pulse runs as jobs** (`engine::pulse`): the method `pulse` everywhere jobs run (`wai-quantum run --method
pulse --pulse OPTIONS`, both MCP servers' run tools, the job service's `/v1/jobs` and `/v1/run`, the page's
`run`). The device comes from a target's calibration snapshot and the options over the defaults, each
parameter saying where it came from; the counts from the captures' joint reading, into the program's bits;
the accuracy class is `bounded`. The result's `pulse.calibration` holds the control program's digest, its
calibrations', the schedule's, the device's and the target snapshot's, `pulse.integrator` the integrator and
its step, and `pulse.density_hash` the final state; the receipt seals them.
- **Schedules and checks**: `wai-quantum pulse schedule` (each frame's timeline, the gate calls, the waveforms)
and `wai-quantum pulse check` (a sealed pulse job held to its request by running it again, each differing
field named); the MCP tools `pulse_schedule` and `pulse_check` on both servers; the service's
`POST /v1/pulse/schedule` and `/v1/pulse/check`; and `QuantumEngine.pulse` in the page. `J040` and `J041` join
the job layer's refusals. `schemas/pulse.v1.json` is new, and the job request and result schemas gain the
pulse options and the pulse result. [The pulse page](wai-quantum/docs/pulse.md) gives the model, the bound
and the options.
- **The Studio's pulse pane**: a pulse program's schedule drawn as a timeline (a lane per frame, each play its
waveform's envelope, captures, barriers, phase and frequency changes, and the gate calls above), a program the
pulse reader refuses answered with its code, an example to load, and a run on the pulse simulator with its
readings, its bound and what its receipt binds, each answer's seal checked in the page; the desktop app serves
the same calls (`pulse`).
- **CI:** `wai-quantum-pulse.yml` runs the 26 tests by name on Linux, the pinned digests on arm64 Linux and on
macOS in debug and release builds, and the tests under wasmtime; `wai-web-wasm.yml`'s pins move to 27 (native)
and 19 (wasm32).
- **Breaking:** `api:rust:wai_quantum::engine::schema::FILES`: the committed schemas grew from 22 to 23 with
`pulse.v1.json`. Code that names the array's length iterates over `FILES` instead.
### wai-quantum: Quantum Studio desktop installers, package-manager entries and signed updates
- **Installers** (`.github/workflows/wai-quantum-desktop-release.yml`, on a tag `wai-quantum-desktop-v<version>`):
the desktop app (`wai/wai-quantum-desktop`, still 0.1.0) built on each runner and attached to the forge release
with each asset's SHA-256, uploaded over the private network: a disk image for macOS on Apple silicon and Intel
(with the GPU state vector); an AppImage, a Debian and an RPM package for Linux on x86_64 and arm64; an installer
and a portable archive for Windows on x86_64. Each build runs what it made first: the bundled app's `check` and
`update-status`, the macOS app's code signature and the disk image's checksums, the AppImage, and the Debian
package unpacked. The tag must equal the version in both `Cargo.toml` and `tauri.conf.json`. The app gains
`icons/icon.ico` and `icons/icon.icns`: a Windows build needs the `.ico` for its resource file, and the app had
not been built on Windows before.
- **Package managers**: `release/manifests.py` writes a Homebrew cask (`wai-quantum-desktop`) and a Scoop manifest
from the assets attached and their SHA-256 files, from templates beside it, and attaches them; nothing is pushed
to the tap or the bucket. The cask says the app updates itself only when the release signed its updates, and
says how to open an app signed ad hoc until one is notarised. `release/manifests_test.py` holds the script to
releases made up here (every platform, none signed, one platform, none at all).
- **Updates**: a copy installed by its own installer (the macOS app, the AppImage, the packages, the Windows
installer) asks the update manifest the website serves (`/quantum-studio-desktop/latest.json`) for a newer
version as it opens, and
the Studio offers it in a bar above the frame (`src/update.js`, its words in English and Chinese). It installs
only when asked, and only an update whose signature verifies with the public key in `tauri.conf.json` and
names the version announced (`requireSignedVersion`); the app then restarts into it. A copy built from source or
unpacked by a package manager does not update itself, and its update check answers why. `wai-quantum-desktop
verify-update FILE SIG VERSION` makes the same check, and the release runs it on every update it signs before
attaching anything; `wai-quantum-desktop update-status` says which installer a copy came from, and the release
and CI ask it of each copy (the app, the AppImage and the Debian package know their installer; a copy cargo
built and the portable archive's know none, so they never update themselves). The release's last job writes
`latest.json` from the update signatures and attaches it to the version's own release, which the website
serves from when it is next deployed.
- **Signing is the owner's to switch on**: each step is skipped, and the log says so, while its forge secrets are
absent. `WAI_QUANTUM_DESKTOP_UPDATER_KEY` (and its password) signs updates; until it is set, a release has
installers and no update. The `APPLE_*` secrets sign the macOS app with a Developer ID and notarise it; until then
it is signed ad hoc. The README gives the one command that makes the update key and every secret's name.
- **CI** (`wai-quantum-studio.yml`): the desktop app's tests, clippy and `check` now run on Linux (in the
act-22.04 image, with the web view's libraries) and on Windows as well as on macOS, each leg requiring the update
tests by name, and the Linux leg runs `release/manifests_test.py`; `test/update.test.mjs` (no browser) holds the
update bar's logic. Dependencies added to the app: `tauri-plugin-updater` 2.14.0, with `minisign-verify` 0.3.0 and
`base64` 0.22.1 used directly, both already in its tree.
### wai-quantum for JavaScript: the engine as an npm package
- **`@transaction-science/wai-quantum` 0.1.0** (`wai/wai-quantum-js/`): the job service's engine for WebAssembly
(`wai-web` with its `quantum_engine` feature, carrying the engine of `wai-quantum` 0.7.0) and its bindings, an ES
module with its TypeScript declarations and no dependencies, for a page or for Node. `build.sh` builds it with the
paths remapped and refuses a local path left in what it ships, and packs it. The package keeps its own version line,
as the Python package does.
- **CI:** `wai-quantum-js.yml` builds and packs it, holds the tarball to the package's allow-list, runs
`test/smoke.mjs` on it unpacked (a job run with a fresh key and its receipt verified, a forged result refused, a
program linted and drawn, a refusal with its code) and the README's Node example on it installed by name.
`wai-quantum-js-release.yml` does the same on a `wai-quantum-cli-v*` tag and attaches the tarball and its SHA-256
to the release. Nothing is published to a registry from CI.
### wai-quantum-cli 0.2.2: the registry's line shown as text
This release changes no code. 0.2.1 put the `mcp-name` line in an HTML comment, but the MCP Registry reads the
README as crates.io renders it, and the rendering drops comments, so the registry could not find it. 0.2.2 shows
the line as code in the README (and its translation), and a test holds the README to that. 0.2.1 went to crates.io
only; the binaries, the Homebrew formula and the Scoop manifest go from 0.2.0 to 0.2.2. The desktop app requires
0.2.2, and both lockfiles that hold `wai-quantum-cli` move with it.
### wai-quantum-cli 0.2.1: the command line on the MCP Registry
This release changes no code; `wai-quantum-cli` 0.2.1 is 0.2.0 with the README line the MCP Registry reads.
- **The MCP Registry lists the job service** as `science.transaction.wai/quantum-jobs` 0.2.0, its endpoint
`https://wai.transaction.science/quantum-jobs/mcp`, under the namespace that
`https://wai.transaction.science/.well-known/mcp-registry-auth` proves.
- **The command line's README carries the `mcp-name` line** that the registry reads from a crate's README to
list `science.transaction.wai/quantum`. 0.2.0's README lacks it, and the registry refused that entry.
- **Both `server.json` files drop `repository`**, which the registry refuses for the forge's address.
- The desktop app requires 0.2.1, and both lockfiles that hold `wai-quantum-cli` move with it.
## v0.4.0
### One catalogue of operations, the agent rules on every command, and the service binaries named `wai-*`
- **The operation catalogue** (`wai::catalogue`): every operation the toolchain offers, once,
named `noun.verb` (`envelope.inspect`, `project.create`, `receipt.verify`), the verb from
naming rule N3's closed list, with its summary, command line, placement, input and output
JSON Schemas, effect, toolset, stability and the build features it needs. It lists the
operations that work today, `wai breaking` (`compat.check`), `wai conform`
(`conformance.run`, `conformance.list`) and the fleet's `wai enrolment create|list` and
`wai report create|export` (`enrolment.create`, `enrolment.list`, `report.create`,
`report.export`, each placed on the client, against a fleet service) among them. Each entry
says when it waits for confirmation (`confirm`). A command a person already types keeps
its spelling (`wai hello` is `toolchain.test`, `wai stage refine` is `stage.encode`,
`wai http seal-playlist` is `playlist.lock`). **`wai ops list`**, **`wai ops search
"<task>"`** (offline, up to five ranked matches), **`wai ops show <id>`** and **`wai ops
export`**, whose output is [`catalogue/ops.json`](catalogue/ops.json).
- **The command line through it.** A command resolves through the catalogue alone, by the
spelling it has today (`wai inspect`) or by its id's words (`wai envelope inspect`); help and
usage come from the catalogue's entries.
- **The agent rules on every command.** Exit codes 0 to 7 (0 done, 1 a check failed, 2 usage,
3 permission, 4 confirmation required, 5 not found, 6 target unavailable, 7 internal);
`--json` prints one envelope, `{"wai": "envelope/1", "op", "ok", "data" | "error", "exit"}`,
from one module that `wai breaking` and `wai conform` share; a command's own JSON (`wai create
--list`, `wai generate`, `wai score`, `wai stage apply`, `wai dev --check`, `wai breaking`,
`wai conform`) is now that envelope's `data`. Without `--json` the output is text, on a
terminal or not. `--dry-run` on every operation that is not
read-only writes nothing and says what it would write. An operation that would replace a
file the command line did not name (`out.wai` when `-o` is not given, an earlier
`wai hello`'s files) asks at a terminal and otherwise exits 4 until `--yes` is given. An
unknown command exits 5, not 2.
- **One confirmation flag, `--yes`.** `--confirm`, which `wai conform`, `wai setup-agent` and
the fleet's operations took, is its deprecated spelling: it still works and does the same,
and `wai --help`, each operation's help and the export (`agent_flags`) say it is deprecated.
Every confirming command a run prints names `--yes`. The fleet's operations, `wai conform`
and `wai setup-agent` wait as their entries say: a fleet service changed, or a file that
exists replaced.
- **The fleet's operations on the same rules**: their JSON is the envelope with `--json` (it was
also printed whenever standard output was not a terminal), a dry run's `plan` names each file
and each request to the fleet service, none of them sent, and a confirmation is exit 4 with
`proposed` and the confirming command. `wai conform` and `wai breaking` print text unless
`--json` is given, as every command does.
- **The doors held to it** (`.github/workflows/wai-catalogue.yml`): the export is the table;
`scripts/check-wai-ops-doors.py` holds MCP tool names, the services' OpenAPI operationIds,
the C header and the Swift and Kotlin sinks' public names to the catalogue. `wai mcp`'s tool
table is held to it both ways (every tool an operation the catalogue says it serves, in that
toolset, and every such operation a tool), and so is the room service's OpenAPI document
(every `operationId` at the catalogue's route, every routed operation described).
- **Renamed to the verb rule (N3)**: the MCP tool `package_build`, which carries a file in an
envelope as `wai wrap` does, is `envelope_wrap`; `room_session` is `session_show`; the room
service's challenges are `watch.setup`, `conversation.setup` and `call.setup` (they were
`watchchallenge.create`, `conversationchallenge.create` and `callchallenge.create`);
`wai mcp` is the operation `mcp.start`. Create's export is also `wai app export`.
- **Breaking**, intended: the module that held the naming rule is replaced by the catalogue,
which holds the rule and every name. `api:rust:wai::ops`, `api:rust:wai::ops::VERBS`,
`api:rust:wai::ops::NAMED_AS_IS`, `api:rust:wai::ops::check_id` and
`api:rust:wai::ops::mcp_tool_name` are gone; use `wai::catalogue`, its `VERBS`, its rule
(`findings_of`) and each operation's name in every door (`Op::names`). The names the rule
once let stand are held to it now, so `NAMED_AS_IS` has no replacement.
- **Breaking**, intended: the room service's three challenges keep their routes and take the
names the rule gives them, `watch.setup`, `conversation.setup` and `call.setup`, as their
`operationId`s: `api:openapi:joule-moq-room:GET /watch/challenge`,
`api:openapi:joule-moq-room:GET /msg/challenge` and
`api:openapi:joule-moq-room:GET /call/challenge`. A client generated from the document calls
them by those names; the requests and answers are unchanged.
- **The service binaries are `wai-relay`, `wai-room` and `wai-ingest`**; their crates keep
their names (`joule-moq-relay`, `joule-moq-room`, `joule-moq-ingest`). `wai dev` and the
quickstart archive use the new names.
- **Every example is Apache-2.0** and carries `LICENSE`, which `wai create` writes into the
project.
- `wai` sends nothing off your machine unless you name a remote service.
### WAI Studio on the web: one front end, its core in WebAssembly, served by `wai dev` at `/_wai/`
- **[`wai-studio`](wai-studio/)**: one front end with two hosts, chosen when the page starts.
Hosted, its core runs in the tab and nothing is uploaded; served by `wai dev` at `/_wai/`, the
local `wai` does what a tab does not hold. Its panels: Inspect (a `.wai` file's envelope,
capabilities, provenance, SPEC §4 verdict and receipts), Verify and receipts, Encode, decode,
package (the floor in the tab, every codec the local `wai` holds through it), Rooms (a room
shown and joined as a viewer, a pre-call test of a relay), Run (`wai dev` only: the stack, its
services on loopback, their logs) and Quantum (Quantum Studio as a panel). Its command palette
lists every operation of the catalogue by its id, and says where one runs that its host does
not.
- **The actions come from the operation catalogue**: `scripts/actions.mjs` generates Studio's
action table from `ops.json`, and `--check` fails on an action with no operation, an operation
placed for the browser with no action, or a mismatched name.
- **The core** (`wai-studio/core`): the `wai` crate with the floor and the receipt checks, bound
for WebAssembly, each call an operation named `noun_verb` and answering the command line's JSON
envelope.
- **`wai dev` serves Studio at `/_wai/`** on the origin it already serves, when Studio's files
are found (`$WAI_STUDIO`, or `share/wai/studio` beside the binary), with the dev stack's own
routes for the page under `/_wai/api/`, answered to this origin alone. It opens no listener.
### `wai breaking` and `wai conform`: the compatibility gate, and conformance anyone can run
- **`wai breaking --against <git-ref>`** (operation `compat.check`) reports what the working
tree breaks against a ref, in three cumulative categories, strictest by default: **wire** (the
registry export: capabilities and their classes, deterministic identifiers, wire identifiers;
the media and delivery corpora), **record** (the domain-separation prefixes, magics and
versions records are built from; the corpora of signed records and their verdicts; the
published JSON Schemas) and **API** (the Rust public API of `wai` and `wai-quantum`, read from
the compiler's rustdoc JSON; the C header `wai-rs/include/wai.h`; the room service's OpenAPI
document). It compares against the merge base of the ref and `HEAD`. Each finding names its
item, its category, what changed and why that breaks; additions are listed as compatible.
- **An intended break passes only when this file names it**: a line the change adds here, in
an entry that says **Breaking**, with the finding's id (as `wai breaking` prints it, such as
api:c:wai_envelope_pack) in backticks. Otherwise the run exits 1.
- **`.github/workflows/wai-breaking.yml`** runs it on every pull request that touches WAI,
against main. WAI's libraries go to public registries behind this gate.
- **`wai conform <kind> --suite <suite> --impl <command or URL>`** (operation
`conformance.run`) runs one of WAI's corpora against another implementation: verifiers
(`claim`, `jwp`, `signer`), a decoder (`score`), a packager (`policy`) and an origin
(`http`). A command-line adapter gives each case as a file (`{case}`, `{in}`), the corpus
whole (`{corpus}`), or standard input, and reads the answer from standard output or `{out}`;
an HTTP adapter posts each case to a URL, or, for an origin, fetches from it. Every
expectation is the corpus's own.
- **The result is a signed record**, `conform-result/1` (`*.wai-conform.json`): the corpus
digest, the implementation (its command and the SHA-256 of each file it names, or its URL),
each case's outcome, the summary and the standard's version, signed with Ed25519 by a key
made for the run or one given with `--key`. **`wai verify`** checks its signature and that its
summary and verdict are what its cases say. CI runs the corpora's independent Python
verifiers and the reference packager through it.
- **[conformance-results](extensions/conformance-results.md)** specifies the record: its
members (§2) and corpus digest (§2.1), its signing input (the domain `wai:conform-result` and
`0x01`, then the record without `sig` in RFC 8785 form) and Ed25519 signature under SPEC §7's
strict rule (§3), what a verifier checks and in what order (§4) and the codes it refuses with
(§5). `conform-result/1` is registered as a document kind. The reference is the `wai-rs`
module `conform_result` (feature `conform_result`, which `quickstart` includes); the corpus,
[`conform-result-conformance/`](conform-result-conformance/), holds accepted records and
refused ones (a case flipped, the corpus digest changed, the summary alone changed, a bad
signature, `S` past L, a key of small order, a summary or verdict that disagrees with its
cases though signed, an unknown version, members missing, added or named twice, a count with
a fraction, a digest out of form, bytes that are not JSON), generated by
`wai_conform_result_vectors`; its second implementation is `conform_result_verify.py`.
- **Measurements are not compared**: `wai breaking` leaves out the corpus files it lists as
records of a measured run (the external decodes of the format probes, the relay's recorded
metering run), each with its reason; the vectors built from them are still compared.
- Both follow the toolchain's agent rules: the JSON envelope `envelope/1` with `--json`, one
exit status for each outcome (0 held, 1 a check failed, 2 usage, 4 a record would be replaced
without `--yes`, 5 not found, 6 the implementation does not answer, 7 internal), and
`wai conform --dry-run`.
### Fleet: an organisation's nodes enrolled by their keys, their records verified, and a report it signs for a period
- **The text** ([`extensions/fleet.md`](extensions/fleet.md), `wai.delivery.fleet`): the
organisation's enrolment of a node, `WFE1` (its key, its role, a label, the span it holds); the
records a node contributes as it signs them: a relay's session summaries
(`joule-moq-relay/session-summary/2`, read in the relay's own form and verified by its own
signature), every hop's per-hop records with their ledgers, and the room descriptors that place
those in time; each record's instant, its period and its enrolment, and one thing counted once;
the fleet report, `WFR1`, with a line per node and per room, sessions, links and bytes, and energy
per scope and acquisition class, never summed across either, every unmetered figure counted as
unmetered and a total over a mix stated partial; citations; the bundle a verifier recomputes the
report from; and the reasons, among them `unmetered_summed` for a report that drops an unmetered
figure or counts it as a figure of zero joules, and `line_unsourced` for a line no record gives.
- **`wai-rs`** (feature `fleet`): the enrolment, the records, the figures, the report and the
bundle's verification; `wai_fleet_vectors` and `tests/fleet.rs`.
- **`wai-fleet`** ([`wai-fleet/`](wai-fleet/)): the fleet as an HTTP service over a directory it
owns, the enrolments and records in append-only logs, every report kept with its bundle; and
`wai-fleet collect`, which gives each line of a node's summary file, a path or an `http` or
`https` address, to a running service.
- **Operations**: `wai enrolment create` and `wai enrolment list` (`enrolment.create`,
`enrolment.list`), `wai report create` and `wai report export` (`report.create`,
`report.export`), on the toolchain's agent rules; `wai verify` checks `*.wai-fleet.json` bundles.
The quickstart build carries the feature.
- **The corpus** ([`live-conformance/fleet/`](live-conformance/fleet/)): a week of two relays
cascading, a compositor and an ingest gateway, with records taken from the metering corpus, the
reference relay's recorded per-hop record among them; a fleet whose energy is whole, one whose
energy is unmetered, and one enrolled and silent; and a control of one defect for each reason.
Its second implementation, `live-conformance/fleet_verify.py`, gives every vector the same
outcome, fails every mutated copy, and catches each of its own checks dropped.
### Create: a WAI app described, built as a model writes it, and made a project by `wai create --from app.oui`
- **The widgets** ([`wai-app`](wai-app/), a library with no binary): a joule-ui registry of WAI
widgets, `App`, `Room`, `WatchTogether`, `Show`, `Player`, `Chat`, `Poll`, `Participants`,
`Receipts`, `EnergyMeter` and `Text`, each backed by the room client of `joule-moq-web`
running in a frame of the page. Each kind of room accepts only the widgets its room service
keeps: a watch room holds no `Chat` and no `Poll`, and a line that asks for one is refused
`disallowed-child` at its line. An app holds one room; a poll has 2 to 32 options; a show's
audience is 1 to 255; a watch room's source is a file within the app.
- **Create**: a page where a person describes an app and a model writes it in joule-ui's line
language ([joule-ui SPEC](../joule-ui/SPEC.md) §11), which the page parses as it streams and
renders widget by widget, every widget validated against the registry. The page calls no
model of its own and holds no key: the visitor's own model (its address and their key,
called from the page alone), their coding agent (`wai mcp --toolset app`, whose bridge on
127.0.0.1 the page reads with a pairing code), or a recorded stream. Every action on the page
is sealed into a hash-chained log the page signs with its own key and verifies
(joule-ui-interaction; the figures are estimates, labelled `Estimator`: a page has no meter).
Export hands over the program, `app.oui`, and the command that makes it a project.
- **`wai create <dir> --from app.oui`** (`app.export`; the feature `app`): an ordinary WAI
project, the same kind `wai create --template` writes: its `wai.json`, written by the one
writer every project file has, names the program (`app`) and the room `wai dev` runs for it
(`dev.mode` `room`, `watch` or `show`, a watch room's file, a show's `audience`); beside it
the program in its canonical form, the page and its runtime under `vendor/` (each file
checked against the runtime's listing), a README and an AGENTS.md. No key is in it.
**`wai app guide`** and **`wai app check`** (`app.guide`, `app.check`): the guide a model
writes against, and the app, the room its project file names and its runtime checked.
**`wai mcp --toolset app`**: `app_guide`, `app_check`, `app_stream` (into the person's open
Create page through the bridge) and `app_export`.
- **Every project `wai create` writes carries its licence**, Apache-2.0, as `LICENSE`, and every
example's README says so.
- **`wai dev --show [--audience N]`** (or `"mode": "show"` in `wai.json`): a room with
co-performance, its relay holding the floor to the room's clock. `wai dev`'s front never
serves a path with a segment that starts with a dot, so a project's `.wai/`, where its room's
keys are, stays off the origin.
### Coding agents: `wai mcp`, `wai setup-agent`, and the room service's API described
- **`wai mcp`** (`mcp.start`; the feature `mcp`, which the quickstart's `wai` holds): WAI's
operations as tools for a coding assistant, a Model Context Protocol server (revision
2025-06-18) on standard input and output. Each tool is an operation of the toolchain's
catalogue, named by it with `_` for the dot, in toolsets. `core`, the default:
`envelope_inspect` (an envelope or a package), `registry_list` and `registry_show` (the
registered capabilities), `receipt_verify` (a delivery receipt, a claim with the context the
tool takes, a live-room session or a stream audit bundle, by `kind` or read from the
arguments; each answer the verifier's verdict and reason code), `envelope_wrap` (a media file
under `wai wrap`'s rules, inside the project directory only), `conformance_list` and
`conformance_run` (a corpus's independent verifier), and `docs_search` (the specification's
text). `dev`, marked development only, on the target `local` alone: `dev_start` and `dev_stop`
(a relay and a room service on loopback), `room_grant` (grants for test principals, refusing
any service whose address is not a loopback address, before connecting), `room_join`,
`room_leave` and `session_show`. `--toolset` chooses; `--read-only` offers only the tools that
read or run a verifier. The verdicts agree vector by vector with jwp-conformance,
claim-conformance, live-conformance/rooms and live-conformance/audit (`tests/cli_mcp.rs`).
- **`wai setup-agent`** (`agent.setup`): writes `AGENTS.md` in a project, adds an import of it to
a `CLAUDE.md` the project already has, and registers `wai mcp` (with `--toolset`, those
toolsets) in its MCP configuration files, by their documented formats; it changes a file that
exists only after its diff is shown and confirmed, has a dry run, and writes nothing outside
the project directory. With `--json` it answers in the toolchain's JSON envelope
(`envelope/1`), with the table's exit statuses: 2 usage, 3 permission, 4 confirmation
required (each proposed change and the confirming command given), 5 not found
(`tests/cli_setup_agent.rs`).
- **`wai mcp` and `wai setup-agent` are verbs of the one table** (`dx::VERBS`) that holds `hello`,
`create`, `dev` and `verify`, and operations of the catalogue (`wai::catalogue`): `mcp.start`,
`agent.setup`, every tool's operation, and the room service's operations at their routes,
held to it both ways by the tool table's tests and the room service's contract test.
- **The room service's API**: `joule-moq-room/openapi.json` (OpenAPI 3.1) describes every route,
request and status of the room service in each of its modes, each operation's `operationId`
its name in the catalogue (`room.show`, `room.grant`, `watch.join`, `message.create`, ...),
held to the code by a contract test that matches the names, the routers' routes, each
operation's statuses against its handlers', and the running service's answers in every mode.
`wai-room dev` runs a development room on loopback for `wai mcp` and for tests.
### The developer's path: `wai hello`, `create`, `dev` and `verify`; the floor with no system library; the examples
- **`floor`** (wai-rs feature): the mandatory floor of SPEC §5 (PNG, FLAC, zstd) decoded with no
system codec library; `wai encode` writes PNG and zstd, and a codec outside the floor is
refused naming the build that has it. **`quickstart`**: the floor, and the receipt checks
`hello` and `verify` run; the prebuilt `wai` is built with it.
- **`wai hello`** names, dispatches, rebuilds and receipts a picture made in place, the receipt
laid out as http-delivery §8 lays receipts beside an object; **`wai verify <dir>`** checks
every receipt index under a directory offline; **`wai create <dir> --template <name>`** writes
a project from one of the examples, whose files the binary holds; **`wai dev`** runs the relay,
the room service and the room page on one loopback origin, and `--check` checks each piece
through it. The project file a template writes is `wai.json`.
- **`examples/`**: video-room, watch-together, messaging, encoder-ingest, package-and-deliver,
verify-receipts, apple-app, android-app and remote-desktop, each with a test that runs it
through `wai create` and `wai dev` (`.github/workflows/wai-examples.yml`).
- **`quickstart/`**: the archive of the command line and what `wai dev` runs, its build, its
release workflow (`.github/workflows/wai-release.yml`, on a tag `wai-v<version>`) and its
installer.
### Contribution links: an SRT sender probes a stalled tail
- **[live-contribution](extensions/live-contribution.md) §4.1:** a receiver finds a gap only behind a
later arrival, so a lost newest packet went unrecovered until it expired. When the acknowledged
position has not moved for 2 × RTT + 20 ms, the sender sends its newest packet again, backing off
to 16 times that wait. The reference sender counts these as `probes`, apart from
`retransmitted`, and reports what it holds `unacknowledged`.
- **§6.1:** a packet whose first arrival is a retransmission, with nothing behind it yet to show
the gap, is **recovered** (found missing as it arrives, a recovery of 0 µs), never delivered at
the first try: its first send was lost. The SRT and RIST receivers count it so; before, a
probed tail read as a first try.
### `wai.live.message` and `wai.live.call`: conversations and calls on live rooms
- **The extension** ([live-messaging](extensions/live-messaging.md)): a conversation is an
end-to-end encrypted room with a conversation statement, `WMC1`, the authority's. Its clock
records membership rather than presence (§3.4): an offline member stays a member. Each member
publishes a messages track, `[room, member, "messages"]`: each group one message, sealed as
confidentiality seals a media track and signed into the member's receipt chain in the
labelled form, receipt alias 5. The plaintext is a message record, `WMG1`, signed by its
author: a text, a reply in a thread, a reaction, an edit or a retraction, each of the last four
naming its target by the link hash of the target's group receipt. A verifier keeps an edited
message's original and states its current text, and keeps a retracted message in verification,
stated retracted, its body shown nowhere. Any member commits, the room service's log ordering
the commits (§7.2, which replaces confidentiality §5.5's lowest-leaf rule in a conversation,
since no member is removed for being offline), and the clock signs an epoch statement, `WME1`,
after each GroupInfo or commit, naming the epoch's roster. Acknowledgements of delivery and
reading, `WMA1`, are signed in the clear. A history store holds the log, ciphertext, receipts
and signed records only, takes a message only from a member of the latest epoch sealed in that
epoch, and serves each principal the entries of the epochs it was a member of (§9.3:
`not_a_member` otherwise). A call is a room with a call descriptor, `WCT1`; the caller's
invitations, `WCI1`, are held in each callee's inbox and checked before a device rings; every
invitation ends with an answer, `WCA1`: accept, decline, cancel, or the clock's missed call at
its deadline.
- **The reference** (`wai-rs`, feature `live_messaging`): every record, sealing and opening a
message object (confidentiality §4, both suites), receipts, the conversation verifier a store
uses for its checks, what a principal is owed, and the call verifier. `joule-moq-mls` gains a
conversation's commit rule (`CommitRule::AnyMember`). `joule-moq-room` keeps a conversation
(`[messaging]`) in a history store on disk that survives a restart, and calls (`[call]`) with
per-principal inboxes and the clock's missed calls. `wai-live-web` gives a page the records and
the verifier (`Messages`, `callCheck`); `joule-moq-web`'s `web/room/messages.html` is a member
in a page and `web/room/call.html` a call's inbox and flow.
- **Conformance**: [`live-conformance/messaging/`](live-conformance/messaging/), written from
fixed seeds by `wai_live_messaging_vectors gen`, every message sealed under its epoch's secret:
conversations accepted and refused, one message refused for each reason a message can raise,
history requests, calls and every record on its own. Its second implementation,
`live_messaging_verify.py`, agrees with the reference on every vector. `joule-moq-room`'s
`tests/messaging_room.rs` runs members through real MLS groups across a restart of the service
and a removal, and `tests/call_room.rs` a call through a relay; the `messaging` and `call`
scenarios of `joule-moq-web/e2e/` run them in a browser.
### `wai.live.remote`: what a controller types is sealed to the host
- **Sealed actions** ([remote-desktop](extensions/remote-desktop.md) §5.5): a host states a
sealing key, an X25519 public key, as the screen statement's last field (`seal`; a statement
without it reads as before). A controller seals every key, text and clipboard action to it: the
action becomes type 8, `sealed`, which keeps in the clear the type it seals, its `dt_us`, a
clipboard text's length and a commitment, and the input statement ends with one HPKE seal of
every sealed action's opening (a 16-byte salt and its fields, a text padded to 64 bytes),
RFC 9180 base mode with DHKEM(X25519, HKDF-SHA256), HKDF-SHA256 and AES-128-GCM, its `aad`
the author's key and the statement's clear part. The commitment, SHA-256 over the room, the
author, the host, the screen statement, the action's index and instant, the type it seals and
its opening, binds the input, its order and its author. Pointer, wheel and touch actions stay
in the clear. Every other field of every record is as it was.
- **Two rules and two levels**: §5.3 gains rule 8, `input_unsealed` (a key, text or clipboard
action in the clear to a screen with a sealing key), and rule 9, `input_bad_seal` (a seal to a
screen with no sealing key, or one that does not open, read or keep its commitments, whose four
reasons are §12's new fourth table). A verification at the commitment level reads no plaintext
and checks every outcome against rules 1 to 8, and rule 9 where the screen has no sealing key;
one at the opened level, given the host's opening key, opens every seal, makes all nine rules
and states each sealed input opened or why not (§11.4). An outcome's `reason` runs to 9; a
host's totals state rules 8 and 9 where they are not zero.
- **The reference** (`wai-rs`): `Input::seal`, `Input::open`, `remote_desktop::commitment`,
`decide_opened` (all nine rules, the actions to inject) and `verify_opened`; HPKE moves from
`telemetry_aggregate` to a crate module behind its own feature, `hpke`, which both use. The
corpus keeps its 42 vectors byte for byte and appends ten sealed ones, each stating the opened
level beside the commitment level, among them a seal to the wrong key and a commitment that
does not match; `remote_desktop_verify.py` checks both levels, opening every seal with the
aggregation verifier's HPKE.
- **The host agent** makes a sealing key for each session, states it, and opens every sealed
input before it injects it (`--opening-key` writes the session's opening key for a verifier);
its headless viewer seals; and it reaches the room service over `https://` and the relay over
`wss://`, verifying each certificate against the system's roots and any `--ca` file. **The room
page** seals what it sends, never sends a key, text or clipboard action in the clear, and shows
a viewer that a controller is typing, never what. **The room service's clock** bounds the
records it holds of a session, per principal first (§10): at `[remote]
max_records_per_principal` (100000 by default) it cuts and removes the principal that reached
it (`leave` reason 2) and the session goes on, the host cutting it at the same record; at
`max_records` (1000000 by default, ten times the bound per principal) it ends the session,
every principal's `leave` signed, and states why.
### `wai.video.encode`: AVC and HEVC through the encoders a device already has, and who made the bytes
- **The extension** ([platform-encode](extensions/platform-encode.md)). WAI writes no AVC or HEVC
encoder. An encode goes through an interface the device has: the operating system's media
framework and its hardware media engine (`platform`), a GPU's encode engine through the video
acceleration API (`va`) or through the GPU driver's own encode interface (`gpu-driver`), or a
software AVC library the user obtains separately and names at run time (`software`). Every
encoder is loaded at run time; nothing is linked or shipped. The output is held to one shape
before it is recorded (§4.3): an ISO/IEC 14496-10 or ISO/IEC 23008-2 Annex B byte stream of one
access unit per picture, an IDR access unit carrying its parameter sets at exactly the planned
pictures and no other random access point, 8-bit 4:2:0 progressive, one layer, no reordering.
A rung of another size is resampled first by `wai.resample.two-tap`, a pure function in
integers.
- **The record** `wai.video.encode` (§6 to §8): a signed statement of the interface, the encoder
and its version, whether a hardware engine coded the pictures, the device and the driver (and
for a library the SHA-256 of the file loaded), the source by digest, the settings, the IDR plan,
the stream by digest, profile, level and tier, and an optional energy figure with
jwp-receipts §2.4.5's members, its counter, attribution and fabrics. A hardware encode's figure
that counts no engine is `Partial`. A verifier checks the record, then the stream against its
digest and §4.3, then the source; it never re-encodes.
- **An encode over a package's pieces** (§9): one floor object per piece, each naming its encoder
and version in `derived_from.renderer` (packaging §7.5) and binding the device and the driver
through its render settings; the record binds the package receipt, each piece's base and each
object. The package itself is unchanged.
- **The reference:** `wai-rs` feature `platform_encode` (the four adapters, the Annex B reader,
the record, the energy counters); `wai encode avc|hevc`, `wai encoders`, `wai encode-record
verify`; `wai-pack encode-rung` and `verify-rung`; `wai_encode_measure` for speed, energy and
quality.
- **The corpus** [`encode-conformance/`](encode-conformance/): small streams made through each
interface (fixtures, pinned by SHA-256), records and negative controls generated over them from
a fixed-seed key, every reason refused at least once, and encoded renditions of the base
packaging golden, accepted and mutated. `encode_verify.py` is the second implementation,
written from the text; it agrees with `wai-rs` and `wai-pack` on every case.
### `wai.live.world`: a shared world in a room
- **The extension** ([live-worlds](extensions/live-worlds.md)): the room authority's world
statement, `WLW1`, names the world a room's operations replay in (for `wai.world.model`, its
model header) and the bounds of an op and of an object. A principal whose grant holds the
attribute `world` = `act` publishes a world track, `[room, principal, "world"]`: each group
one op batch, receipted as a room's media track is, receipt alias 4. A relay maps the
attribute onto PUBLISH of that track alone, and its door holds each world track to its
owner's key. The room's clock records an actor's `join` when its world track appears and its
`leave` when it ends, and no `publish`. A participant that enters late fetches every world
track from group 0; a relay keeps them for it. In an end-to-end encrypted room the world
tracks are sealed as confidentiality seals a media track, and each member carries every op it
holds into each epoch it reaches, so a member that joins later replays the world from its
start; that member follows every actor's track before it publishes its own, since its own
track records its join and a carry is published once. A verifier checks each object's
receipt under its owner's chain, then each op's signature, owner, kind, Lamport order and
equivocation, and replays the merged ops to the session hash. A world track is counted with
a room's other tracks in the per-hop record.
- **The reference** (`wai-rs`, feature `live_world`): the statement, a participant's copy of
the world, the carry and the verifier. `wai-live-web` gives a page the same copy (`World`,
`worldStatement`, `worldAction`). `joule-moq-relay` maps the attribute; `joule-moq-room`
keeps world rooms (`[world]`, with or without `e2ee`); `joule-moq-web`'s
`web/world/world.js` is a participant in a page, and its signer can sign objects that are
not envelopes.
- **Conformance**: [`live-conformance/worlds/`](live-conformance/worlds/), written from fixed
seeds by `wai_live_world_vectors gen`, every refusal of §9.2 and §3.4 among its negative
controls, and a public world and a private one accepted; its second implementation,
`live_worlds_verify.py`, agrees with the reference on every vector. `joule-moq-room`'s
`tests/world_room.rs` runs actors and a late joiner through a relay, and the `world` and
`privateworld` scenarios of `joule-moq-web/e2e/` run them in a browser.
- **Text made exact**: interactive-worlds §5.1 states that a `CausalOp`'s integers are
little-endian and that its signature is over the domain, then every byte before it.
live-rooms §4.4, §6.1 and §10.2 and confidentiality §1 name the world track and point here.
### `wai.live.remote`: a remote desktop in a room
- **The extension** ([remote-desktop](extensions/remote-desktop.md)): a host publishes its screen
as the room's `screen` track and signs screen statements (`WDS1`) naming what it captures, the
frame size every input's coordinates are in, the actions it injects, the oldest an action may
be, whether it keeps a floor, and the encoder that made the frames with its version, as
packaging §7 (paragraph 7.5) has a floor name one; no rule reads the encoder, and no verifier
recomputes a frame. Input statements (`WDI1`) carry up to 32 pointer, button, wheel, key (HID
usage IDs), text, touch and clipboard actions. A host decides each input by the seven rules of
§5.3 at its place in the merged order and signs an outcome (`WDO1`) for every input it
receives, anchored on the first frame it captured after injecting it, or refused with the
rule's number; a verifier recomputes it. Control passes by grant exchange or by floor
statements (`WDF1`). Latency records (`WDL1`) state an action's instant and its frame's
presentation on the viewer's estimate, signed on their own, and a verifier states the three
legs. Two grant attributes, `remote_host` and `remote_control` (the control power), and no new
power bit, since live-rooms §3.4 refuses one.
- **The reference** (`wai-rs`, feature `remote_desktop`): the statements, the rules
(`remote_desktop::decide`, which the host agent runs), and the verifier; the corpus
`live-conformance/remote/` (`wai_remote_desktop_vectors gen`): a floor session and an open
session whose control passes by grant exchange, a gap, and refused vectors giving every reason
of §12, among them a host applying an input that each rule refuses; and its second
implementation, `remote_desktop_verify.py`, which shares no code with `wai-rs`, agrees with it
on every vector, fails every mutated copy of the corpus, and catches each of its own checks
dropped.
- **The host agent** (`joule-moq-remote`) captures a screen, encodes it with the platform's
encoder, publishes it to a room through `joule-moq-relay` with its receipts, decides every
input with `wai-rs`'s rules and injects what they accept; the room page of `joule-moq-web`
shows a host's screen, sends input under its grant, and signs latency records.
- **A floor statement the floor's rules refuse changes nothing** (§7.2, §7.3): it stays in its
author's chain, a verifier states it with its reason (`floor_not_permitted`,
`floor_not_eligible`, `floor_not_holder`, now §12's third table) and computes the floor without
it, and a host gives it no effect, so a holder's release that crosses the host's take on the
wire leaves a session that verifies. `wai-rs` exports the rule (`remote_desktop::floor_rule`),
which the host agent and the room service's clock both run. The corpus states `floor_refused`;
its six vectors of a refused floor statement, their rooms unchanged, are accepted, and three
whole floor sessions follow them: a give by a controller that does not hold the floor, a
release by one, and a late release.
- **Remote rooms a room service keeps** (`joule-moq-room` with `[remote]`): the service grants
the host, each controller and each viewer for a key the visitor makes, hands control over by
the host's grant exchange, watches the host's screen track, takes every principal's records in
their chains' order, and serves the session's records to a principal of the room alone; the
host agent joins such a room (`run --room`), and the room page makes its key and asks for its
grant.
### `wai.live.contribution`: SRT and RIST into a room's tracks, and the link record
- **The text** ([live-contribution](extensions/live-contribution.md)): SRT
(`draft-sharabayko-srt-01`) as a listener and a caller, and RIST (VSF TR-06-1:2020 Simple
Profile, VSF TR-06-2:2024 Main Profile), onto a room's tracks through the ingest gateway. A
publisher POSTs `/srt` or `/rist` under its room grant and proof of key and gets a one-time
session: the stream ID and passphrase live-ingest §13's listener checks, in a stated order with
its rejection codes; or the gateway calls the publisher's listener, where its configuration
lists the address; for RIST a port of its own, the CNAME its RTCP must give and, in the Main
Profile, a PSK passphrase. The transport stream is demultiplexed: the first H.264 or HEVC
stream and the first AAC stream, one access unit or one ADTS frame an object, a PES missing a
packet dropped whole, the PTS on the room clock; the tracks, groups, catalog and first-hop
receipts are a WHIP session's, with an owner's `link` track beside them.
- **The link record**, `WCL1`, which the gateway signs part by part: each part covers the
sequence numbers passed while it was open, each one delivered at the first try, recovered,
dropped or outstanding, with its loss runs, requests, retransmissions, late arrivals,
duplicates, round trips and recovery times; parts chain by digest and name the head of every
media track's receipt chain at their end; each is published, with receipts, on the link track.
A verifier reads a part in stated steps (`record_too_large` … `runs_invalid`) and a series
(`chain_broken`, `sequence_gap`).
- **Live-ingest** §13 and §17: SRT is implemented as live-contribution profiles it.
- **The reference implementation**: `joule-contrib` (SRT and RIST, the transport stream, the
account; RIST behind the feature `rist`, off by default, live-contribution §10) and
`joule-moq-ingest` (the sessions, the record, `joule-moq-ingest-link-vectors`). Interop with
each transport's reference tools run locally as test peers, byte for byte, in the clear and
encrypted, through lossy paths, in both directions, and into the gateway with an encoder's
H.264, HEVC and AAC.
- **The corpus** [`live-conformance/links/`](live-conformance/links/) and its second
implementation `live-conformance/live_contribution_verify.py`, written from the text alone.
### Enhanced RTMP ingest, and `wai.live.injection`: SCTE 104 automation into signed cues
- **Enhanced RTMP v2** ([live-ingest](extensions/live-ingest.md) §12, until now a mapping):
implemented in `joule-moq-ingest`. An encoder publishes over RTMP or RTMPS with a one-time
stream key the gateway's `/rtmp` resource issues to a request that proves its room grant's
key (§12.1); the stream key is good for one publish within its time, and the grant is
admitted again when it is used. The gateway answers the handshake, the chunk stream, the
protocol control messages and the commands, declares `capsEx` and the FourCCs it carries in
its `connect` result, reads `onMetaData` per track, and carries legacy AVC and AAC and the
enhanced `avc1`, `hvc1`, `av01`, `vp09`, `vp08`, `Opus`, `fLaC` and `mp4a`, single-track,
multitrack and in aggregate messages, through every ModEx (§12.2, §12.4). Each track is
bound to a room track at its first packet: a video track's layer is the grant's publish
profile layer its `onMetaData` entry matches, and what matches none, or is not audio track 0,
or is a codec the profile does not list, is counted and not carried (§12.3). Each frame is
one object in its capability's framing, at the room-clock time §7 maps its decode time to,
with its composition time offset as the new `live.cto` member (§6, §12.5); the gateway signs
every group's receipts as the first hop, as for WHIP. At shutdown a publisher that declared
`Reconnect` is sent the reconnect request, and its key is good once more (§12.8). `vvc1` is
mapped and not carried. Read against one encoder's output, two forms the E-RTMP table does
not show are read too: a FLAC sequence start of STREAMINFO alone, and a coded-frames packet
of no bytes at the end of a stream (not carried).
- **`wai.live.injection`** ([cue-injection](extensions/cue-injection.md)): the gateway is an
SCTE 104 injector. It answers the automation system's API connection with SCTE 104's
responses and result codes, translates splice, time-signal and splice-null requests and
their avail, DTMF, segmentation, time, tier and private descriptor requests into SCTE 35
sections (ANSI/SCTE 35-1 2023r2) on the room's 90 kHz clock, and derives each request's
cues: an out and an in from a splice insert, the in an auto-return implies, and the outs,
ins and chapters of a time signal's break, placement-opportunity and chapter descriptors.
It signs each cue when its splice instant comes, holding its own chain to live-ads §4.3's
break rules, and before each cue its chain carries the cue's injection record `WIJ1`: the
message verbatim, its arrival and the parameters it was translated with, from which a
verifier recomputes the cue (§8). The request whose fields are SCTE 35-1 §14.1's sample
builds that sample's section byte for byte. SCTE 104 in a video signal's ancillary data
(SMPTE ST 2010) is a mapping note (§10).
- **Corpora and second implementations**: `live-conformance/ertmp/` (RTMP message sequences
of one publish, with their arrivals, and every object each room track carries) and
`live-conformance/injection/` (translations, refusals with their result codes, scenarios on
a clock with the chain and the breaks they make, and chains a verifier refuses), each
generated through `joule-moq-ingest` and regenerating byte for byte;
`ertmp_verify.py` and `injection_verify.py`, written from the texts and sharing no code
with the reference, agree with it on every vector.
- **Tests**: `joule-moq-ingest`'s `tests/ertmp.rs` (a publisher of its own over RTMP and
RTMPS: every payload framing, multitrack layers, the refusals of a stream key, the reconnect
request), `tests/scte104_injection.rs` (an automation system of its own over TCP, the chain
read at a relay), `tests/conformance_corpus.rs`, and `tests/ertmp_ffmpeg.rs`, which runs a
local encoder as the test client where one is present (AVC with B-frames, HEVC and AVC on
the platform's media engine, HEVC in software, AV1, VP9; AAC, Opus, FLAC) and decodes what
the relay carries in a conformant decoder.
### Live rooms across relays: hop links, the evidence at every hop
- **A room across relays** ([live-rooms](extensions/live-rooms.md) §10.7): a room may be carried
by several relays connected by hop links. Each relay holds the sessions that reach it to §10
(rights, the seek power, a rated track's threshold); a hop link runs under its operator's token,
never a principal's grant; a relay that forwards a FETCH or a fill of a track whose live edge it
does not know learns it upstream and holds the request to §10.6 before forwarding; a hop link's
threshold is 0, so a rated room is served at the relays its rated publishers reach; and no relay
forwards a request back where it came from, to a relay it has passed through, or past a bound,
the relays it passed carried on it in a Message Parameter negotiated by a Setup Option
(`draft-ietf-moq-transport-22` §9.20). `relay_loop`, `relay_hops_exceeded` and `no_upstream`
are a relay's reason phrases, like `seek_not_granted`, and no verifier's.
- **Receipts at every door** ([jwp-receipts](extensions/jwp-receipts.md) §4.1): a relay holds
what another relay forwards it to its door as it holds a publisher's, and refuses an object
that does not verify though the relay before it forwarded it.
- **One record per hop, chaining by track** ([live-metering](extensions/live-metering.md)
§3.10): every relay signs its own record; a hop link is counted in both ends' track entries
and in neither's subscriber sessions or ledger, so each subscriber is counted once and a
grant's consumption is what the relays serving its sessions state; each relay's energy is its
own figure, summed only within a class. §8 states what the reference relay counts. The record's
layout, the receipt and the corpus do not change.
- **The floor and the inputs, once** ([co-performance](extensions/co-performance.md) §8): the
relay a performer's or an author's session publishes to decides the floor and the inputs; a
relay that receives them over a hop link forwards them as decided, in the order they arrive,
and reads the statements of the events tracks it forwards. Neither `wai-rs` nor
`co_performance_verify.py` changes: a verifier counts each input where the merged order meets
it, wherever it was decided.
- **A DVR window across relays** ([live-recording](extensions/live-recording.md) §8.3):
fetched through another relay as an upstream FETCH, the seek power held at the viewer's relay.
- **The reference relay** (`joule-moq-relay`): `[[upstream]]` links over native QUIC,
WebTransport or the WebSocket binding, the relay-id path, failover, GOAWAY across hops, and
`tests/cascade.rs` for each of the above.
### The native sink: a platform's own player, verified and evidenced
- **The extension** ([native-sink](extensions/native-sink.md)): what a native host's sink does for a
stream the host plays through its platform's player, which is not changed and decodes on the
platform's media engine. The sink sits on the path of the player's requests. It verifies a media
playlist against its playlist receipt and the chain of its live revisions (http-delivery §8.6),
the host fetching each parent the chain asks for; it holds an object whose URL states its content
hash (a WAI object's cid, a floor fragment's SHA-256 name under a signed playlist or by the
owner's word) to that hash; and it tells the host, object by object, whether it may hand the
bytes to the player, a refusal withheld and answered with a failed request (§4). It signs
stream-audit's acknowledgements, claims and session record under a fresh key per session, keeps
the observation log in order and closed at its end (§5), ends a session nobody can see or whose
viewer is idle (§6), and states the sink decode and the display as the unmetered marker where the
platform's decoder runs outside every counter an application reads (§7). One C interface serves
every native platform (§9). No record of its own.
- **Reference**: `wai-rs` module `native_sink` and its C interface `ffi_sink` (feature
`native_sink`, pure Rust, no codec library), `include/wai.h` regenerated, the generator
`wai_native_sink_vectors`, `tests/native_sink.rs` (every corpus case through the C symbols).
- **Corpus** [`live-conformance/native-sink/`](live-conformance/native-sink/): scripts of a host's
calls with the answer each gets, over the reference package's floor fragments: a signed on-demand
presentation, a fragment changed in transit, receipts that do not hold, a live chain walked
through a parent, WAI objects held to their cids, an alias rebound, a stream of another service
with a key and a marked ad, sessions ended hidden and idle, energy entries, the log's order, and
refused calls and configurations. Its second implementation, `native_sink_verify.py`, replays
every case from the text, holds every bundle to its own session field by field, verifies it with
stream-audit's verifier, and catches every mutation and every dropped check. The page's
integration of stream-audit replays one case call for call and signs the same bundle byte for
byte (`wai-audit/web/native-sink.test.mjs`).
- **The Swift package** (`wai/wai-sink-swift`): the wrapper, the loopback relay, the player
session and the platform's attention signals, with `scripts/build-xcframework.sh` for every
slice; its tests replay the corpus through the wrapper, run the relay over the reference floor,
and play a presentation made with the platform's own encoder through the sink to its end, with a
changed fragment withheld and a session nobody can see ended as hidden.
- **The Kotlin library** (`wai/wai-sink-android`, an AAR): a JNI layer over the C interface
(`jni/wai_sink_jni.c`, every status carried to Kotlin with libwai's message), libwai and that
layer built for `arm64-v8a`, `armeabi-v7a` and `x86_64` with a pinned NDK and linked for 16 KiB
pages; the wrapper, the loopback relay (a random 128-bit path token, 502 for anything refused),
the observation loop, the collector client, the player session over the platform's media player
library, and the attention signals from the activity's lifecycle, the window's and the display's
state, the view's place in its window and the window's input. Its unit tests run on the host JVM
through the JNI layer over a host build of libwai: every corpus case and refused configuration,
the `page-parity` bundle byte for byte, the relay over the reference floor, and the loop's
`hidden` and `idle` endings. Its instrumented tests, in a headless emulator, run the corpus and
the relay on the device, play the floor (AV1 in CMAF) through the sink to its end, refuse a
changed fragment on every retry, end a session nobody can see as `hidden`, and read the
attention signals from a real activity. The extension now says that a window shown without the
input focus is shown (§6.2), and that a platform's exception for unencrypted HTTP covers the
loopback address alone (§11).
### `wai.delivery.audit`: an audit kit for streams another service delivers
- **The extension** ([stream-audit](extensions/stream-audit.md)): what a content owner gathers
from the players of a stream another service packages and delivers over HTTP, asking nothing of
that service. A player makes a fresh key per session and signs settlement acknowledgements of
every object it fetched, one chain per origin and window, a repeat in a window counted and not
listed (§4); a presentation claim (`WPC1`) for each ad an `EXT-X-DATERANGE` marks, its `cue` the
marker's digest and its creative the acknowledged segments the ad covers (§6); and the session
record `WAS1` (§5): the channel, the owner's collector, the spans presented with their least
visible fraction and audibility, the start time and stalls its delivery-telemetry log recomputes
(§7), the energy per role (origin, delivery, sink decode, display) with each figure's class or
the unmetered marker (§8), how the session ended, and the digests of everything it names. The
bundle (§9), its verification, with reasons of its own beside the codes it borrows (§10,
§14), the channel report the collector signs and anyone holding the bundles recomputes byte for
byte, its rebuffering suppressed below 50 sessions (§11), and the collector (§12). No price,
tariff or amount.
- **Settlement** ([settlement](extensions/settlement.md) §14): acknowledging the objects of an
HTTP service that delivers no WAI envelopes. Such an object is a response's content with its
content codings removed, a range response's range an object of its own, named by plain SHA-256;
its upstream is named by the digest of its origin, signs nothing and appears in acknowledgements
alone; no receipt is checked, no range proof applies and no statement is signed. The rest of §3
is unchanged.
- **Reference**: `wai-rs` module `stream_audit` (feature `stream_audit`), the generator
`wai_stream_audit_vectors`, `tests/stream_audit.rs`; registered `wai.delivery.audit` (record) and
the magic `WAS1`. `live_metering` gains a crate-visible reader of a figure and its class, so the
record's energy entries are read by the per-hop record's own rules.
- **Corpus** [`live-conformance/audit/`](live-conformance/audit/): sessions (accepted: two origins
with two marked ads, an on-demand session ended `hidden` with an `OnChipCounter` figure and an
attachment, one that presented nothing, one with a claim of every verdict, and a channel of more
than 50 sessions; refused: one control per defect) and reports (rebuffering suppressed and stated, and four
refused). Its second implementation, `stream_audit_verify.py`, reads acknowledgements, claims and
logs with the settlement, ad-presentation and live-metering verifiers, and catches every mutation
of the corpus and every one of its checks dropped.
- **The kit** (`wai/wai-audit`): the collector (`wai-audit serve`: verify, store once per session,
CMCD intake, signed reports; per-client and body limits; CORS for the configured page origins) and
its command line (`verify`, `report`, `check`); the player integration `web/audit.js` with a
network tap for any web player and a loader adapter, whose tests rebuild the corpus's records
byte for byte; and a browser end to end (`e2e/run.sh`) through an open-source web player, in the
workflow `wai-audit.yml`.
### `wai.video.keys`: the proxy form, and a signing proxy in front of any licence server
- **The extension** ([key-release](extensions/key-release.md)) adds form 2 of the
`wai.video.keys` receipt, signed by a proxy in front of a licence server for every licence
request it answers, a release or a refusal, under a domain of its own; form 1 keeps its bytes
and its identity. A receipt names each key's **scheme** (ISO/IEC 23001-7's `cenc`, `cbc1`,
`cens` and `cbcs`; the HLS methods `AES-128`, `SAMPLE-AES` and `AES-256-GCM` of
`draft-pantos-hls-rfc8216bis-22` §4.4.4.4), the **key delivery** (an HLS key file, W3C Clear
Key, or a content protection system by its DASH-IF system ID), which deliveries carry which
schemes (§3.3), and the SHA-256 of the **CPIX document** the keys came in (DASH-IF CPIX 2.3,
ETSI TS 103 799).
- **Records**: the key holder's commitment list `WKL1`, derived from the CPIX key material
(§4.4: each key's KID, scheme, period and commitment), so the proxy copies every `key_commit`
and holds no key; the entitlement `WKE1`, signed by the operator's entitlement authority and
bound to a key the viewer's device holds; the device's proof over each request (§6.1, SHA-256
and Ed25519, which a browser computes with the Web Cryptography API); and the release ticket
`WKT1` (§9.3), with which a key server redeems a key delivered in the clear once, so the proxy
never relays one.
- **The decision** (§7.2) is a pure function of those signed inputs, the proxy's clock and its
earlier receipts, checked in order: an entitlement that does not hold, a stale request, a
replay, an unknown key, the wrong class, an entitlement not yet valid or ended, a key period
the entitlement does not meet (key rotation, §7.4), an offline licence the entitlement does
not allow, and a renewal of no licence, past its count or after the licence lapsed. An offline
licence never outlives its entitlement (§7.3). A refused request is never forwarded, and a
key is released by the licence server's 2xx answer or by a ticket, never by the proxy's word
(§8.4 rule 8).
- **The reference** is the `key_release` module of `wai-rs` (feature `key_release`), with its
own CPIX reader, which refuses a document type declaration and reads no encrypted key; the
corpus is [`live-conformance/keys/`](live-conformance/keys/) (releases over each delivery,
every decision, an expired entitlement, rotation over three periods, offline licences and
renewals, a licence server that refused, and negative controls giving every reason of §11),
and its second implementation is `live-conformance/key_release_verify.py`.
- **The service** is `wai/wai-key-proxy`: the proxy, which appends each receipt to a durable,
single-writer log before it answers, reads it back on a restart, and orders a replay that
arrives while the first request is being forwarded after it; the key server; and tools to
derive a list from a CPIX document, issue an entitlement, sign a request and verify a log.
Its tests run all of it on loopback against a fixture licence server.
### `wai.delivery.ledger`: invoice lines recomputed from signed records
- **The extension** ([receipt-ledger](extensions/receipt-ledger.md)): a tariff, `WLT1`, that a
seller and a buyer both sign, pricing in one currency every unit WAI's records state: a room
session's units (live-metering §4.4), acknowledged bytes by object class (settlement §3.3), a
package's packages, entries and stored bytes (packaging §9), and ad impressions, presented under
the players' claims or billable on beacons, or presented in a stream another party stitched
(stitched-presentation §7.4); and naming the rooms, delivering parties, packagers, and ad
deciders and stitchers it bills, the stitchers being a stitched verification's signers. §3 is the note on one tariff across rooms, packages and HTTP delivery:
a room receipt or a statement that states a tariff states this one (`tariff_conflict`), every
amount rounds down once per line, and no unit is converted into another or priced from joules.
- **The invoice**, `WLI1`: every record it was computed from; one line per source and unit, each
citing its records by a citation digest; the joules per scope (room hops, delivery hops,
packaging, ad decisions) and per class, with the largest declared uncertainty, never summed
across either; chained per tariff so that no record is billed twice (`record_rebilled`). A
verifier verifies each source by its own extension and recomputes every figure, refusing with
fifteen reasons of its own (§10). Two reports are stated with an accepted invoice: the ad
decisions against the presentation claims, with the reason each billed impression did not count,
and for a stitched stream stitched-presentation §7.5's own statement, unchanged;
and delivery by party under the one tariff, with free and declared bytes and the overlaps, bytes
one receiver acknowledged from two parties in windows that overlap.
- **The reference**: `wai-rs`'s `receipt_ledger` module (feature `receipt_ledger`); the corpus
`live-conformance/ledger/`, its sources taken from the metering, settlement, ads and presentation
corpora and the packs of pack-conformance, with two delivering parties' statements written for
it; `live-conformance/receipt_ledger_verify.py`, the second implementation, which verifies each
source with that corpus's own second implementation and checks a single verification document
with `--document`.
- **The service**, `wai/wai-ledger`: tariffs, sources and invoices kept in a directory it owns,
each file written whole and the state recovered and verified again on start; every invoice
served with its verification document. Its tests issue the corpus's invoices byte for byte and
recover the chain after a restart; `smoke.sh` checks it over HTTP; CI in
`.github/workflows/wai-ledger.yml`.
### `wai.video.stitched_presentation`: player-signed presentation of ads another party stitched, and the signed pod-input record
- **The claim** ([stitched-presentation](extensions/stitched-presentation.md) §5): `WSC1`, a
player's signed claim that it presented one ad written into an HLS or DASH stream, in its
segments or as an interstitial. It names the ad by the marker the stream carries for it (§3):
an `EXT-X-DATERANGE` tag's `ID` and `START-DATE` and the ad identity an attribute of it
carries; an interstitial date range's `ID` and `START-DATE` and the index of the asset the
player was served, whose `URI` is the ad identity; or a DASH event's Period, scheme, value and
`id`, with the ad identity an attribute of it carries. It states the SHA-256 of the manifest
it read the marker from, and carries ad-presentation's spans, viewability rules, quartile and
attestation under a session key made for one playback session, with a redemption context of
its own.
- **The pod-input record** (§4): `WSI1`, signed by the stitcher or by a proxy in front of the ad
server for each decision: the digest of the response (a VAST document, read; a VMAP document,
bound; or a form-1 `wai.video.ssai` receipt, by its identity), the ads chosen with their
markers, ad identities, identities in the response (`AdServingId` or the `Ad`'s `id`),
universal creative identifiers, creative digests, durations and prices per presented
impression (from the response's CPM `Pricing`, checked; a winning bid, its document bound by
digest; or stated by the signer), the avail, the session it was decided for or none for a
break every viewer is shown, the measurement terms of ad-presentation §8, and the frame.
- **Composited frames** (§6): a record's frame is the full picture, a squeeze or a double box;
the ad's region is live-compose §5.9's, the claim states the region's share of the picture,
and a player measures the fraction of the region in view. One presentation is one claim per
signing player.
- **Verification** (§7): each record is checked against its response; each claim against its
record (the session it was decided for, or a shared break) and the manifest's bytes, which
must carry the marker with that ad identity; then each ad's presentations (one own claim a
session), the quartiles they reached, and the spend that counts, by currency and never summed
across currencies. An ad whose segments were fetched ahead of its break and never presented
has no claim and counts nothing; a form-1 receipt's stated quartiles and billable spend are
stated beside the presented ones.
- **A form-1 receipt's JSON carriage** (§4.5, `SsaiReceipt::to_json` and `from_json` in
`src/wai_video_ssai.rs`), so a verifier can be given one; its signed bytes and identity are
unchanged.
- **The reference** is the `stitched_presentation` module of `wai-rs` (feature
`stitched_presentation`, no new dependency), with readers of HLS attribute lists, asset lists
and an XML subset of its own. `wai_stitched_presentation` writes and checks the corpus,
verifies a bundle, signs a pod-input record from a draft, and serves `POST /v1/verify` on a
loopback address. `live-conformance/stitched/`, written from fixed seeds: stitched HLS with
per-ad date ranges and a prefetched ad never presented, an interstitial with attested
presentations and a winning bid's price, a squeeze on a DASH channel, a double box in another
currency, and a stitcher's form-1 receipt whose beacons counted a prefetch; a control of each
record and claim check. Its second implementation is
`live-conformance/stitched_presentation_verify.py`. The browser client's reference producer,
`joule-moq-web/web/ads/stitched.js`, reads the markers from the corpus's manifests, measures
an ad's region, and writes and signs the corpus's claims byte for byte.
### `wai.live.compose`: ad frames, a squeeze and a double box
- **The layouts** ([live-compose](extensions/live-compose.md) §5.9): layout 3, a squeeze, the
program in a window in one corner over the ad, which shows in the L around it; layout 4, a
double box, the program and the ad in two boxes side by side, each tile 16:9 (or of the
descriptor's aspect) in its box. The ad participant is the one whose `pip_attr` attribute is
`true`; the program, the first of the others, a featured one first. The frame gives the
program's tile, the ad's tile, and the ad's region as rectangles, and its share of the
picture, as integer functions of the descriptor's fields, which stitched-presentation's
records reuse. §3.3 holds a frame's fields: two tiles, a window smaller than the canvas both
ways, a double box's side. The corpus gains squeezes in every corner, double boxes on both
sides and under aspect auto, the program alone, the ad alone, a featured program, and four
descriptors refused `compose_invalid`; `live_compose_verify.py` lays them out itself.
### Energy honesty: the energy order of dispatch, a metered relay, `HwShunt` closed to new profiles, and what fewer bytes do not save
- **The energy order** (SPEC §4, §7.1 step 2). A sink tries the capabilities an envelope
offers in the energy order unless its deployer configures another, an order over what it
advertises and never a reason to advertise: `first` for classical decode the platform
answers supported, smooth and power-efficient, `smooth`, `classical` (any other classical
decode, every classical capability of a sink with no platform to ask, and a learned
capability whose decode the sink holds a qualifying measurement of), and
`learned-without-budget` last; the envelope's order within a rank. A `WAI1`'s fallback is
tried first when it ranks before the capability, and the capability after it if it does
not decode, so the order never makes inert an envelope either could decode. In a `WAI2`
the entries never picked keep their places. So a learned capability without a
qualifying measurement is never decoded above a classical fallback the sink can decode.
A sink that takes the manifest's order says so where it reports a decode.
- **The measurement a learned capability needs** is a sink-metered figure of its decode, in
browser-sink §6's energy entry: it qualifies with the class `OnChipCounter` or
`CalibratedInstrument` and the coverage `Whole`. The order reads only whether one is held,
never its value, so a figure still changes no route (browser-sink §6). A deployer may
compare it with a ceiling per decode as its own policy; the default does not.
- **The browser sink** (browser-sink §4.7): objects that share a `choice` are alternatives
of one content (a `WAI2`'s entries, a `WAI1`'s capability and fallback, a track's
renditions), ordered inside the routing: an alternative routed `ua` ranks by the user
agent's answer, one routed `host` or `wasm` ranks `classical` whatever the user agent said
(AV1's user-agent tier is `none`, and a decode-equivalence need with no probe run routes
elsewhere), and one routed `none` comes last; the page decodes the first that routes
(`no_alternative` where none does). `browser_sink::choose_report`, the page's
`chooseReport`, and a `choices` section in `live-conformance/browser/` with each
alternative's rank and synthetic measurements marked as such, checked by `wai-rs`, by the
page's wasm build in a browser and by `browser_sink_verify.py`.
- **Platform decode capabilities** ([platform-decode](extensions/platform-decode.md)): the
query a page asks Media Capabilities and WebCodecs, read from the entry's payload (the AV1
ISOBMFF binding's codec string from the sequence header, the Annex E.3 string from an
HEVC SPS), never from what an encoder declared; and the answer entered in browser-sink's
report, `supported` from `isConfigSupported()` alone and `smooth` and `power_efficient`
from `decodingInfo()` once it said supported, so a Media Capabilities `supported: false`
overrides nothing. `powerEfficient` and `prefer-hardware` are hints: never an energy
figure, never evidence of hardware. The extension defines no order and no second meaning
of advertising: both are browser-sink's. `wai::platform_decode` (`query`,
`report_answer`), `platform-decode-conformance/queries.json` (two packaging-corpus floors
and an SPS the platform's encoder wrote with `prefer-hardware`), and `wai-web`'s
`platform_decode` bundle (`platformQuery`, `platformAnswer`, `platformChoose`), now built
in CI. In a browser on an arm64 desktop, every AV1 floor of the packaging corpus decoded
through WebCodecs with its payload's configuration, and an HEVC stream from the platform's
encoder decoded back with the codec string read from the stream.
- **Reference.** `wai-rs` `energy_order` (`DispatchPolicy`, `PlatformAnswer`, `Rank`,
`read_measurement`, `qualifies`); `Wai::dispatch_verified` and `WaiMulti::pick` apply the
energy order, `dispatch_with` and `pick_with` take another, and the HTTP client's default
order and the C ABI's `wai_envelope_select` follow it. The HTTP corpus's O2 pin cases and
the pin corpus's selection cases state the table's order they run in; `select_O2_default`
and `fetch_O2_default` are the default.
- **`HwShunt` is closed to new profiles** (energy-measurement §2.6, *New profiles*): a
profile, record, claim, table or registration member defined after the hardware class was
refined refuses `HwShunt` from every producer and reads it as no other class; the profiles
that could have sealed it keep their acceptance, listed by name. `wai-rs`
`energy_class::check_new_profile` (`LegacyInNewProfile`). A package counter read through
RAPL or a system management unit is `OnChipCounter`.
- **The reference relay meters itself** (live-metering §8). `joule-moq-relay` samples a
counter every 100 ms: its process's own CPU energy on macOS (scope task), or the CPU
packages' RAPL energy on a Linux host the operator states runs it alone (scope package).
A room's record carries the advance over its span as `OnChipCounter` with filtering
`Unknown` and a declared uncertainty, where the counter was live and the relay served
that room alone; the unmetered marker otherwise. A session's summary stays unmetered.
`[metering] energy = "auto" | "off"` and `dedicated_host`; a macOS CI job requires a
measured figure, and a host with no counter prints SKIPPED.
- **No modelled or zero figure shown as measured.**
- The C settlement kernel (`oer-conformance/settle/`) graded every receipt `measured`
from `hardware` whatever its meter; it now writes the grade and device the meter states.
The host harness's figures are constants, so they are `projected`, from a `simulation`,
by `model_estimated`, and `firmware_vectors.json` is regenerated.
- `wai-web`: the world demo seals its page-declared estimate labelled `Estimator` and
shows it as one; the quantum demos seal the unmetered marker in place of invented
constants.
- The quantum studio shows a figure with its class, coverage and uncertainty; the quantum
CLI prints the unmetered marker as unmetered, not `0 uJ`; the metering tools head their
bracket as a bracket and print the unmetered marker on stdout when they seal it; a
provenance total names the class it reads at.
- The reference meters (`wai_meter`, `wai_video_meter`, `wai_world_meter`,
`wai_quantum_meter`, `wai_prov`, and `wai_stage_measure --energy macmon`) sealed
`OnChipCounter` from the processor power a non-privileged reader reports, a channel
they never read. On macOS 27 the processor channels of IOReport's `Energy Model` group
stop updating for such a reader, and the reader takes its power from another report it
selects. The meters now seal `ModelBased`, naming the reader in the measurement report
as the method. `energy_burst::PowerSource` says what a reader's samples are, and
`Bracket::on_chip_figure` refuses `OnChipCounter` from a reported power whatever its
brackets show (energy-measurement §3.1, §2.9). A RAPL reader's bracket, the counter's
own differences, still seals `OnChipCounter`. No committed record changes class: the
provenance vector's two figures are unlabelled and keep their bytes.
- **What fewer bytes do not save.** Steering by joules per delivered byte (energy-steering
§5.5), the attribution of a hop's joules to grants by bytes (live-metering §4.6,
live-rooms' `joules_ceiling`) and an HTTP client's joule budget (http-delivery
Appendix F) now say what their figures cover: a hop's own energy or a producer's, not the
network's, which is largely flat against the traffic it carries. The honest gains of
fewer bytes are cost and a mobile radio's draw; the energy that changes is the sink's
decode path and display.
### `wai.sink.energy`: what decoding cost at the sink, per segment, and one session split by hop
- **Sink energy profiles** ([energy-measurement](extensions/energy-measurement.md) §9). For each
kind of platform: the counter a sink reads for its decode, the fabrics it covers (`cpu`,
`gpu`, `media`, `npu`) and what else it sums, whether the reading is the sink's own (`task`:
the operating system's account of the sink's processes, `ri_energy_nj + ri_billed_energy −
ri_serviced_energy` on macOS) or a counter every user shares, from which the figure is §3's
bracket over the whole decode and no fabric's share, and the shortest window. No counter is
ever divided among its users by their busy time; a whole rail is one user's only where that
user was its sole user, at least 99% of the busy time (`rail`). `macos-task` is the kernel's
per-process energy with the work billed to the process (`proc_pid_rusage`,
`RUSAGE_INFO_V6`); `macos-gpu` is IOReport's `GPU Energy`; `linux-rapl` is the powercap
package zone; `linux-nvml` is the NVIDIA total energy counter behind a plausibility rule.
All are `OnChipCounter` with filtering `Unknown`, and none is ever `HwShunt`. On the
reference macOS sink every other `Energy Model` channel was frozen for an unprivileged
reader, and no channel names the video decoder, so a hardware decode path is `Partial`: it
does not cover the media engine. Platforms with no rail (phones, a page) state an
`Estimator` by `estimator-cpu-time/1` with its inputs and a stated uncertainty; the display
is an `Estimator` of its own, `estimator-display/1`, from the panel's declared on-mode power
and its luminance setting, never added to a decode figure.
- **The extension** ([sink-energy](extensions/sink-energy.md)) registers the record
`wai.sink.energy` with three JSON records signed with Ed25519 over RFC 8785:
- a **decode table** (§4): one figure per registered capability, decode path and profile,
per completion of a stated work item, from an odd number of bracketed rounds (at least
seven), each opened from rest. Every round's values are in the record and every counter
reading is in a readings file the record binds by digest, so a verifier recomputes each
round, each round's figure and least declaration (energy-measurement §6.2), the median,
the range, the row's verdict and its declaration, which also covers the rounds' spread.
A row with no decoder, no work item, no hardware path or no live counter is absent and
says which. The procedure `wai-sink-table/1` (§4.4) is the binary `wai_sink_table`;
- a **segment receipt** (§5) per segment of each stream, chained, whose figure is
`measured` over a bounded window (exactly the segment's decode, so no completion-count
term), taken from the sink's own table (`ModelBased`) or another key's (`Estimator`),
estimated, or `unmetered` with its reason;
- a **session receipt** (§6): the per-hop split over the hop roles a stream log uses
(stream-termination §2 and §3.5: 1 origin, 2 relay, 3 sink decode, 4 display), one table
with its hop entries. Each hop is a figure with its own class, the unmetered marker where
the sink knows it took place and has no figure (with the reason), or absent where the sink
states nothing. An origin or relay entry restates a hop's record (a live-metering `WMH1`
for a relay) with its class only where the session was the hop's sole user, at least 99%
of its busy time; a shared hop is the marker `hop_shared`, never divided by time. The
sink-decode entry is composed from the segment receipts, each of which is that hop's
figure for its segment, and no total is stated across classes.
- **Corpus and verifiers.** `live-conformance/sink-energy/` holds accepted and refused tables,
segments, chains and sessions (every reason of §8 refuses at least one) and the measured
tables of the reference sinks under `tables/`, each with its readings and its procedure.
The corpus regenerates byte for byte (`wai_sink_energy_vectors`), and its second
implementation, `live-conformance/sink_energy_verify.py`, written from the text, recomputes
every round, row, segment and split in integers and requires every mutated copy to fail.
CI runs the integer checks in `wai-conformance.yml`, and the procedure on the `macos` and
`linux-hw` runners in `energy-hardware.yml`, where a runner with no live counter reports
the measurement as skipped, never as passed.
- `browser-sink` §6: a page may take its `energy` measurement from a `figure` row of its
sink's decode table.
### `wai.live.termination`: what nobody watches is ended with a receipt, and no bits above the panel
- **The extension** ([stream-termination](extensions/stream-termination.md)) registers the
record `wai.live.termination`: a sink's **stream log**, `WSL1` records chained by digest
and signed under a key it makes for its playback session. The **open** record states the
stream (a MoQ track by its track digest, an HTTP stream by its content description), how it
started (a user activation, or autoplay), the viewer's **autoplay setting** beside the
platform's answer, the termination policy (the grace an unseen video stream gets, an idle
timeout, an autoplay grace), the display declaration and the ladder offered. A **segment
receipt** per segment states its objects' digest, bytes and media span, the rendition, its
match against the panel, and each hop's joules (origin, relay, sink decode, display) with
its acquisition class, the unmetered marker where a hop was not measured and nothing where
the sink knows nothing of it. **Observations** record the document's visibility, occlusion
(the presenting element out of the viewport, or the platform's report), the display's
power and the viewer's interaction, at most once a second. The **termination** states its
reason (`hidden`, `occluded`, `display_off`, `idle`, `autoplay_unattended`, `user`,
`ended`), the media time and the joules of each hop up to it, one total per hop and class.
- **Verification** (§6): the chain and its order; nothing after a termination
(`after_termination`: a stream terminated as hidden sends no further segment receipt); the
reason against the state the log's own observations give at the termination
(`reason_unsupported`; `reason_not_applicable` for hidden audio, which someone may still
hear); a start by autoplay against the setting (`autoplay_against_setting`); every
segment's match and admissibility; and the totals (`totals_mismatch`, a sum across two
classes among them). An accepted log states its totals and unmetered counts, the absent
hops, the panel counts, the unseen time and the **unwatched segments**: those received after
the sink's own grace had run out while nobody could see them.
- **Display capability** ([capability-constraints](extensions/capability-constraints.md) §4):
a sink declares its panel's resolution (sides without orientation), refresh rate, bit
depth, dynamic range and physical size, and how a page reads each. A rendition's
exceedances against it are four bits; the admissible renditions are those within it, or the
least alone where none is; a sender sends no other, and a segment receipt records the match.
The register gains `wai:transfer`, the coding-independent `TransferCharacteristics` code
point. Physical size is recorded and bounds nothing in this revision.
- **Reference.** `wai-rs`'s `stream_termination` module (feature `stream_termination`, with a
sink's writer); `wai-live-web` builds it for a page (`StreamLog`, `panelMatch`,
`verifyStreamLog`) and `wai_live_web termination-verify` for the host. The room page
(`joule-moq-web`'s `web/room/termination.js`) keeps a log for each track it receives,
declares its panel, ends a video stream that has been hidden or out of view for its grace
(the subscription cancelled, the audio kept) and opens a new log when it is seen again.
- **Conformance.** `live-conformance/termination/` (matches and logs, every reason refusing a
log), `stream_termination_verify.py` as its second implementation, and `joule-moq-web`'s
end-to-end scenario `termination`, in which the receiving page's tab really goes to the
background.
### The integer decoders' narrow kernel sets: the same bytes, measured again
- **Kernel sets** (`wai-rs` `int_kernels::Isa`, chosen by the processor's features or by
`WAI_INT_ISA`): narrow convolution kernels on NEON, AVX2, AVX2 with VNNI, AVX-512 and
AVX-512 with VNNI. Each convolution's sums are formed from 16-bit operands (an input past
2^15 − 1 split into two 15-bit limbs) in 32-bit partial sums over blocks of taps sized so
that no partial sum leaves `i32`, then in 64-bit sums under the bound the interpreter
already requires; the inverse GDN's norms run in 64-bit SIMD lanes on x86_64, its roots
are seeded from the IEEE-754 square root and corrected in integers, and `rescale`,
`clamp`, `relu` and `leaky` run on the fast workers, the requantisation in `i64` where its
values fit. The wide kernels stay as the `wide` set, and run wherever a narrow plan does
not apply. No output byte, hash or conformance vector changes.
- **Tests.** Every decoder-level equivalence test runs each kernel set the processor runs
(`int_kernels::equivalence_policies`); `tests/int_ops_conformance.rs` decodes every graph
vector of `ops-conformance/` through the fast kernels under each and holds it to the
stated integers; random geometry against the exact sums, the block and limb edges, the
seeded root, the requantisation and the element-wise operators each have their own test.
- **Measured** ([research/determinism-cost.md](research/determinism-cost.md) §4.6, the same
record and procedure): the 192 `int_hyper` payloads decoded again on every platform with
each kernel set, identical to the record's decodes; and the record's time and energy runs
repeated beside the record's own build and the float decoder, on x86_64 host A, x86_64
host B (the integer decode only) and the build host.
[determinism-tiers](extensions/determinism-tiers.md) §7.1 quotes them.
### What exactness costs: a measured record, and what WAI claims from it
- **The record** ([research/determinism-cost.md](research/determinism-cost.md),
`research/determinism-cost/record.json`): `wai.neural.int_hyper` against its float model,
the pretrained `mbt2018_mean` at all eight published qualities, each checkpoint pinned by
SHA-256, on the 24 self-test pictures of the pinned reference set. It gives, from readings
it keeps: the BD-rate of the integer codec against the float codec by PSNR and by MS-SSIM,
per picture and summarised; each codec's decodes repeated on every platform reached (CPU,
WebAssembly and GPU for the integer decode; CPU and GPU for the float decode) and compared
byte for byte, the float ones placed in or after the entropy decode; and decode time and
energy for both, each energy figure with its counter, scope and class, bracketed by idle
windows, over seven rounds.
- **What WAI claims** ([determinism-tiers](extensions/determinism-tiers.md) §7, new): the
tier's claim rests on the integer construction, not on the record; the record's figures
hold for the pictures, models, platforms and builds it names, and a receipt never takes
its energy from it. Every figure the text quotes from the record carries a marker that
`tools/wai_determinism_cost_verify.py` checks.
- **Tools.** `tools/wai_determinism_cost.py` (pictures, pinned checkpoints, integer export
per quality, both codecs, the float decode on any device, distortion, BD-rate, the record),
`tools/wai_det_energy.py` (a decoder as a child, timed in rounds with its counters read
around them) and `wai-rs`'s `wai_det_cost` bin (feature `neural_int`, `ops_gpu` for a
GPU), which decodes `WIH1` payloads through `int_payload::decode_int` and serves timed
rounds. `tests/det_cost_bin.rs` holds the bin to the committed `int_hyper` vector and every
`WIH1` refusal fixture.
- **Verifier and CI.** `tools/wai_determinism_cost_verify.py`, written from the procedure
and sharing no code with the tool that wrote the record, recomputes every summary value
from the readings, with its own interpolant and integration for the BD-rate, and checks
each quoted figure; the conformance workflow's independent-verifiers job runs it and the
bin's test.
### `wai.avatar.face`: a talking head from a signed keypoint track, the same bytes on every sink
- **The capability** ([avatars](extensions/avatars.md) §6 to §13) registers `wai.avatar.face`, a
payload capability of class `GenerativeDeterministic`. A participant commits once to an
**identity prior**, `WFI1`, a reference picture and its keypoints named by SHA-256, and sends
**keypoint segments**, `WFK1`: the first frame's coordinates in full, the rest as Exp-Golomb
coded deltas, in multiples of a stated step of a sixteenth of a pixel. Every sink generates
the same frames from the segment, the prior and the **generator**, `WFG1`, the capability's
parameter set of one file, pinned by SHA-256: **face-equivalence**, a case of
generation-equivalence.
- **An integer generator.** The driver warps the reference picture to the driving keypoints with
weights from integer-operators §2's exponential, a displacement rounded half away from zero and
a bilinear sample in sixteenths of a pixel, and draws a map of each keypoint (§7.5). The
network is ten 3 × 3 convolutions in a small encoder and decoder, lowered by `face-int/1` to
one `WHG1` graph of `wai-ops/1-i32` operators (§7.6): weights at 2^12, biases at 2^27, Q15
activations, one rounding per operator, and a mask from a `lut16` logistic table whose every
entry is an integer computation. The frame is the warped picture and the generated one blended
by the mask. The graph meets `acc64`.
- **The same frames on every fabric.** The registered generator's frames, and a seeded
generator's, are the same bytes on the CPU, under `wasm32-wasip2` (the corpus regenerated by
its generator built for wasm32 and run under wasmtime), and on a GPU with every graph on the
device (Metal, at `acc64`, every graph compared with the interpreter), and the corpus's second
implementation gives them in Python's integers (§7.8).
- **A generator trained here.** The open keypoint-driven face-reenactment generators were surveyed
with their weights' licences first (§7.9): those whose weights carry a permissive licence need
operators `wai-ops/1` does not define, and the others carry a non-commercial licence or none
for their weights. `tools/wai_avatar_face_train.py` trains a generator on faces it draws itself,
with the integer arithmetic emulated in a second phase, and measures it: PSNR and SSIM of the
integer generator against the floating-point network and against the rendered faces, the
keypoint error of a separately trained detector, and the keypoint track's bitrate (§7.9).
- **Who drove which face.** The participant's key signs the **identity record**
(`wai-face-identity`) and each segment's **track record** (`wai-face-track`, chained by `seq`
and `prev`, optionally stating each frame's SHA-256). That signature is provenance, not a
permission rule: a track record signed by another key, or a segment that names another prior,
is refused (§8.3).
- **Synthesis.** Every segment carries an origin claim of class `synthesized`, step `generate`,
naming the generator and the track record (§8.4); the segment's envelope generates, so a claim
of any other step is `class_understated`. A player's `present` entry names the frames' digest
at `generation-equivalence`, a verifier that recomputes the frames refuses one that presents
other pictures (`present_output_irreproducible`), and a label policy is held to synthesis §10
(`label_not_shown`) (§8.5).
- **Mappings** (§11): to ISO/IEC 23090-39, read through the openly readable 3GPP TR 26.813
V20.0.0 §6.3.4 and draft-ietf-avtcore-rtp-avatar-02 §4, its text not being openly readable
(the identity prior as a component of a base avatar, the generator as a scheme of
`proprietaryAnimations`, a frame as a landmark animation unit, the identity record beside
`authenticationFeatures`, and what the unread text leaves open); to the draft's RTP carriage;
and to the report's two-dimensional avatars, Avatar Representation Assertion, feature
verification and evaluation framework.
- **Conformance** ([`live-conformance/avatar-face/`](live-conformance/avatar-face/)): the trained
generator, two held-out faces and their tracks, and the training report, each pinned by
SHA-256; the files of §7.2 to §7.4 read and refused; frames of a seeded and of the registered
generator, by digest and, for the seeded ones, in full; and cases accepted and refused with
every code of §10 and synthesis's `class_understated`, `present_output_irreproducible` and
`label_not_shown`. `wai_avatar_face_vectors gen|verify`, `tests/avatar_face.rs` and
`tests/avatar_face_gpu.rs` (feature `avatar_face`), and the second implementation
`live-conformance/avatar_face_verify.py`. CI: a step of `wai-conformance.yml`'s independent
verifiers, its job `avatar-face-wasip2`, and a step of `wai-ops-gpu.yml`'s `metal` job.
### Registrations: JPEG XS, MV-HEVC and a projection descriptor, IEEE 1857.11 as a candidate; header probes; CMSD reading; LOC carriage
- **New capabilities** (SPEC §5; the registry export). `wai.image.jxs` and `wai.video.jxs`, JPEG
XS (ISO/IEC 21122): one codestream, or codestreams one a frame in `wai.video.av1`'s framing;
`patent-pool`; `standard-defined`, because a sink that advertises either decodes at ISO/IEC
21122-4's strict conformance point (Annex B.2: decoded images bitwise identical to the
reference), which the registration requires. `wai.video.mvhevc`, MV-HEVC (ITU-T H.265 Annexes F
and G): the Annex B byte stream of a multiview bitstream, `wai.video.hevc` its usable fallback;
`patent-pool`; `standard-defined` on H.265 (V11) §F.13.5.1. `wai.meta.projection`
([projection](extensions/projection.md)): a companion descriptor, `WPJ1`, 40 bytes, saying how a
video rendition's pictures map onto what a viewer sees (rectilinear, equirectangular,
half-equirectangular, fisheye; angles in 2⁻¹⁶ degrees as ITU-T H.274 §8.15 states them) and how
its two views are arranged (packed side by side or top and bottom as ITU-T H.273 §8.4 names
them, or in two layers of a multiview bitstream); royalty-free. `wai.image.ieee1857_11`, IEEE
1857.11-2024 neural-network-based image coding, registered as a **candidate**: the standard is
not openly readable, and its open reference software writes a picture header with no start
code, from a working draft; `NeuralFloat`.
- **Header probes** ([format-probes](extensions/format-probes.md)) for those capabilities and for
`wai.video.hevc`, `wai.video.vvc`, `wai.video.lcevc`, `wai.audio.mpegh`, `wai.video.avs3` and
`wai.audio.avs3`, which were registered with none: each identifies a payload as its
capability's format and reads the parameters its headers state (profile, level, sizes,
layers and views, sampling, configurations), or refuses it with a code of its §12. Each
probe states its basis: the standard's own text (H.265, H.266), open implementations that
agree where the standard is not openly readable (JPEG XS, LCEVC, MPEG-H, AVS3, IEEE 1857.11),
or WAI's own format. For JPEG XS the probe gives the media-type parameters of RFC 9134 §7.1
and states when a stream is the constant number of bytes a frame SMPTE ST 2110-22 carries.
`wai-rs`: module `format_probe` (feature `format_probe`), tool `wai_format_probe`.
- **The corpus** [`format-probe-conformance/`](format-probe-conformance/): vectors written by
permissively licensed encoders at pinned revisions from synthetic pictures (H.266, MV-HEVC
with its base layer, JPEG XS image and video, AVS3), with the facts each encoder was asked
for and every command and decode in `provenance.json`; generated vectors for LCEVC, MHAS, AVS3
audio, the IEEE 1857.11 header and the projection descriptor; negative controls refused with
every code; five LCEVC streams fetched by digest (`fetch.sh`); and `format_probe_verify.py`,
written from the two extensions.
- **Measured for JPEG XS** (format-probes §3.6): the open decoder's C and SIMD paths give
identical output on every JPEG XS vector; a second, independent decoder gives the same bytes on
every vector whose profile it implements, and a lossless vector decodes to its source picture.
- **CMSD reading** ([delivery-telemetry](extensions/delivery-telemetry.md) §6.6): a reader of
`CMSD-Static` and `CMSD-Dynamic` back into the descriptions §6.1 to §6.3 write, passing over
other senders' keys, refusing with `cmsd_malformed`, `cmsd_type` or `cmsd_version`; the fields
and outcomes in `telemetry-conformance/cmsd-read/`, and `cmsd_read_verify.py`. §10 lists the
extension's CMCD and CMSD custom keys in full, and the registry lists each as a wire
identifier of the new kind `telemetry-key`.
- **LOC carriage** ([loc-carriage](extensions/loc-carriage.md)): an envelope whose payload is one
WebCodecs chunk (an Opus packet, an AVC or HEVC access unit, an ADTS frame) as one object of a
track of packaging `loc` (`draft-ietf-moq-loc-04`), its manifest the Immutable Property
`WAI_MANIFEST` and its receipt commitment `WAI_RECEIPT`, rebuilt by a reader byte for byte and
checked against the receipt. The writer and reader are `joule-moq`'s `loc` module; the corpus
is `live-conformance/loc/`, with `loc_verify.py`.
### Priors in tensor files and NNC, and scenes in MPEG-I scene description and USD
- **Tensor files as prior forms** ([prior-carriage](extensions/prior-carriage.md) §9 to §14).
`wai.prior.safetensors` and `wai.prior.gguf` are registered prior forms: a pin's digest covers
the whole file. Each layout is read strictly (a name twice, overlapping or uncovered bytes, a
header past the file are each refused with a named code) and written canonically, and a reader
refuses a file that is not canonical, so one set of tensors has one digest in each form. The
registry lists both forms as established exact: reading hands the decoder each tensor's bytes
as the file holds them.
- **The integer-quantised profile `wai-int/1`** (§10): every tensor a signed integer the kernel
reads as it is, every scale itself an integer tensor, nothing floating point, so loading does no
rounding. Two bindings map native integer artifacts to named tensors and back, byte for byte:
`wsr1/1` (the `WSR1` prior of exact-upscale) and `whm1/1` (the `WHM1` model of
integer-payloads). The two trained `WSR1` priors of the exact-upscale corpus, carried in each
form, load to the same native bytes and give the same upscaled pictures.
- **NNC read and written** (§12 and Appendix A): the subset of ISO/IEC 15938-17 an open
implementation of its 2024 edition reads — the start, model parameter set and topology units,
and data units of integer tensors under the standard's context-adaptive arithmetic coding or
of raw 32-bit floats. Quantised payloads, dependent quantisation, block scans and the other unit
types are refused with `nnc_not_read`, naming what was met; the standard's own clauses were not
read. Measured against that implementation (§12.4): its decoder reads our files to the same
integers, its encoder writes byte-identical data units, and our reader reads its bitstreams.
- **Scene carriage** ([scene-carriage](extensions/scene-carriage.md)): a world, an audio scene,
a world's assets and timed media items written as an ISO/IEC 23090-14 glTF document
(`MPEG_media`, `MPEG_buffer_circular`, `MPEG_accessor_timed`, `MPEG_audio_spatial`,
`MPEG_texture_video`) and as a USD layer of the USD Core Specification v1.0.1 (prims,
translate operations, time samples, asset references by `urn:wai:asset:`), each carrying the
WAI bytes exactly. Reading a document WAI wrote gives the same bytes, and is refused unless
writing them again gives the document; a document WAI did not write becomes new WAI bytes
under stated rules, refused where a rotation, an inexact position, a time between samples, a
gain in decibels or media that names no WAI object would not carry. Meshes travel by content
hash, as a world names its assets.
- **Reference implementation and corpora.** `wai-rs` features `prior_wire` and `scene_map`;
corpora [`live-conformance/prior-forms/`](live-conformance/prior-forms/) and
[`live-conformance/scene/`](live-conformance/scene/), with negative controls for every code,
and their independent verifiers `prior_forms_verify.py` and `scene_verify.py`.
### `wai-ops/1-npu` on a neural processor, and where its arithmetic is not exact
- **`wai/wai-ops-npu`**, an operator implementation of `wai-ops/1-npu` (integer-operators §9.2) for neural processors: a kernel for one compute core of a tile array, built at run time with the array's open toolchain, with a host side built against the device runtime. The interpreter runs each graph and asks the core for every operator that computes values:
- `conv` and `conv_transpose` as sums over taps, into 32-bit accumulator lanes for operands that fit 8 or 16 bits and 64-bit lanes for 32-bit operands, the width the certificate's `mac` record allows;
- `rescale`, `chscale` and `mul` through the vector unit's multiply and shift-round step, under the mode that is the operator's rounding;
- the elementwise operators, and `lut16` a table block at a time.
The plan, the checks, the range policies, the movement operators and the state stay the interpreter's, so the device adds no refusal.
- **Measured: where the unit is not exact.** The unit's shift-round step is `R_floor`, `R_half_up` and `R_half_even` (§2) under three of its modes, at shifts 0 to 59 only. At 60 to 63 every mode shifts left by `64 − s`. 36 vectors of the subset have such a shift, and the kernel forms those in the core's scalar unit. A 32-bit accumulator lane keeps the low 32 bits of a sum past i32. 110 of the 458 graphs that fail the subset by `acc32` alone gave other results on the device. Integer-operators §9.5 has the table, with time and energy, each energy reading named by class: the package counter is `OnChipCounter` (energy-measurement §2; its scope is the whole package), the runtime's own estimate is `ModelBased`. No speed or energy claim is made: the device path is bound by its dispatches.
- **Results by neural processor (§9.5):** on PCI 0x1022:0x17f0, every graph vector in the subset gave the committed result and the interpreter's: 733 vectors, 1,005 decodes. Every one of twelve kernel edits was caught by the corpus. No other neural processor was run.
- **The Web Neural Network API (§9.5):** a per-operator account from its Candidate Recommendation Draft of 21 October 2026. Its convolution, matrix and quantize operators are floating point, so the subset is exact there only when built from its `"int32"` element-wise operations and reductions; a multiplier needs `"int64"`, which the draft allows but does not require. No browser leg was run.
- **`ops-conformance/npu.jsonl` (§14.5):** each graph vector's place in the subset, from its graph's own verdict. The generator writes it and the independent checker writes it again, and both must give the committed bytes. The device test runs exactly the vectors it lists.
- **determinism-tiers §5 and §6** record the device run. No decoder runs its graphs on a neural processor yet, so the tier is not claimed for a decode there.
- **CI:** `wai-ops-npu.yml` runs the probes, the corpus, the kernel edits and the width test on the `linux-amd` runner's device, which must be present. On `linux-arm64` it requires that the device tests skip without `WAI_OPS_NPU_REQUIRE` and fail with it.
### GPUs: when `decode-equivalence` may be claimed, and WGSL's integer semantics on every adapter
- **Claiming the tier on a GPU** ([determinism-tiers](extensions/determinism-tiers.md)
§5). A decoder claims `decode-equivalence` for output whose graphs ran on a GPU only
when every shader module that ran them is integer only (no floating-point type, so no
reassociation or fusion can change a result), uses no subgroup or quad operation and
enables no extension, has no result that depends on scheduling (one atomic kind per
word, its result unused, no atomic load or store; no out-of-bounds access), and is
translated with the translator's integer
guards on: signed arithmetic through unsigned, guarded division and remainder, `abs` and
negation of the most negative value. Where the translator does not produce WGSL's
result, the module does not reach the operation: naga 30.0.1 passes shift amounts to
MSL, HLSL and SPIR-V unreduced, a signed left shift as it is, and integer `clamp` as
MSL's and HLSL's `clamp`, so every shift amount is masked below 32, no left shift is of
a signed value, and every `clamp` has literal bounds in order. Before an adapter runs a
graph it passes WGSL's integer semantics vectors and the implementation's probe; the
claim names the adapter and the translator (or, where the backend reports no PCI
identity, the adapter's name and the system's build). The conditions open with a general
form for any accelerator, of which these are the GPU's.
- **WGSL's integer semantics vectors** (`wgsl-conformance/`). Every integer operation a
kernel may use, on `i32`, `u32` and their two-component vectors, at the cases WGSL defines at run
time and MSL, HLSL and SPIR-V leave open, each result written from the WGSL text
(Candidate Recommendation Draft, 21 October 2026). Cases the kernels never reach are
marked `unguarded`: reported, not deciding.
- **`wai-ops-gpu`.** Discovery runs the semantics suite on each adapter after the
arithmetic probe, and refuses an adapter that differs on a guarded case. Every run-time
shift amount in the kernels is now masked (`& 31u`) and the two run-time clamps are
`min(max(…))`; WGSL gives the same result either way, and the translated code is then
defined on every backend. `tests/wgsl_rules.rs` holds every shipped module to the
conditions on naga's IR and in its MSL and HLSL translations (signed arithmetic found by
naga's wrapping, atomic loads and stores refused), with planted violations refused; the
browser page runs the suite through the browser's WGSL compiler and checks the vectors
against the reference compiled to wasm32. Shown in the forge's run of `wai-ops-gpu.yml` on
Metal and on a browser's WebGPU on that GPU: every guarded case equal, every unguarded one
too, and the capability corpora decoding with their graphs on the device to the CPU
decode's bytes; and on Vulkan with an integrated GPU of PCI vendor 0x1002 and llvmpipe on
Linux, the crate's tests with none differing. The tier is claimed on those, each run's
report lines committed in `wai/research/ops-gpu-runs.txt`; adapters run by hand with no
record, and those G1 showed on the kernel text before the masks, are not claimed until a
recorded run.
### Packaging: each step on the fabric that does it with the least energy
- **Steps and fabrics** ([packaging](extensions/packaging.md) §11.6). The computations of
a pack and a verify are named steps (`source-read`, `segment`, `base-search`,
`base-code`, `refine-code`, `sse`, `object-hash`, `digest`, `floor-read`,
`floor-decode`, `index`, `io`, and `source-decode` before a pack), and each may run on a
CPU, a GPU, a media engine or a neural processor (`npu`). Every step is defined in integers (and
AV1 decoding by its decoding process), so where it runs changes no byte of a package
and no verdict: §11.2 holds across fabrics. A packager runs a step on a backend only
after it has given the reference's output on qualifying inputs; a verifier computes a
check on another fabric only where the package states the value it compares with, and
decides any mismatch again on the reference. No hardware encoder computes a WAI step; a
floor encoded on one is a §7.5 floor. A floor states no decoded-output digest, so a
hardware decode never decides a floor verdict.
- **The fabric record** ([jwp-receipts](extensions/jwp-receipts.md) §2.4.8,
[energy-measurement](extensions/energy-measurement.md) §6.3). A measurement claim may
carry `fabrics`: each step with every fabric that ran it and its backend, and the
figure divided into one share per fabric, attributed `task` (the operating system's
account of the work's own energy) or `rail` (the fabric's whole rail, the work its sole
user, read by sampling every user's busy time through the span). A rail shared with
other work is never divided by busy time, and a fabric that ran a step has a share of
more than 0. `fabrics` is the one claim member that is absent rather than null, so every
existing claim and package keeps its bytes; a verifier before this revision refuses a
claim that carries it (`unknown_member`). New reasons: `measurement_fabric_unknown`, `measurement_fabric_order`,
`measurement_fabric_unmetered`, `measurement_fabric_unused`, `measurement_fabric_sum`.
- **wai-pack** places each call of a step on the fabric whose measured cost for its size
is least (`--fabric cpu|gpu|auto:<costs.json>`, `wai-pack fabric list|calibrate`), the
CPU on a tie, where unmeasured, and for every call another fabric does not take. With
feature `gpu`, the motion search, SSE and the object hashes run on a GPU through
**wai-pack-gpu**, integer WGSL kernels through wgpu, shown exact on Metal (Vulkan and
DX12 are built and not run, and nothing is claimed for them). With feature
`fabric-meter` on macOS, `--meter fabric` meters a pack per fabric without privileges
(the task's own and billed CPU energy, and the GPU's whole rail where the pack was its
sole user) and seals an `OnChipCounter` figure with a declared uncertainty and the
claim's fabric record. `wai-pack floor-decode` decodes a
package's floors with libdav1d or the media engine and prints each piece's digest, for
comparing decoders. The Python verifiers stay on the CPU and share no code with wai-rs;
`claim_verify.py` and `pack_verify.py` read the fabric record.
- **Conformance.** Claim vectors of the fabric record appended after the existing ones, which a test
pins by digest. wai-pack-gpu's kernels are held to references transcribed from the text
and every edited kernel is caught; wai-pack's fabric differential packs every golden on
a GPU byte for byte and gives every package and staged mutation the CPU's verdict.
### Packaging: a title's whole ladder in one package
- **Several floor rungs per package** ([packaging](extensions/packaging.md) §7.1). A
component may have any number of floor rungs; each piece then has one floor object per
floor rung, in ladder order, all over one base, so the base of a ladder is coded once.
Each floor rung has a **floor name**: the component's id when it has one floor rung (so
existing packages are unchanged, byte for byte), the rung's id when it has several. The
name names the supplied stream (`<name>.obu`, §7.3), the CMAF files (`floor/<name>/`,
§7.6), the floor playlist (`hls/classic/floor-<name>.m3u8`), the DASH representation
(`<name>-floor`) and the MoQ track (`<name>/floor`); a floor name that cannot be one path
segment, or two equal in any case, is refused (`pack-id`). The classical master lists one
variant per floor rung; with several, `dash/classic.mpd` carries each representation's
codecs and size on the representation. The verifier holds the *k*-th floor entry of each
piece to the *k*-th floor rung.
- **A re-pack from a parent** (§11.5). A packer may take every piece's base and
refinements from an earlier package instead of coding them (`wai-pack pack
--base-from <package>`), so a title's integer base is coded once and taken by every
package made from the same source after it. It refuses a parent that does not verify
(`repack-parent-invalid`), one whose pieces, kinds, rungs, parameters or deadlines are
not this pack's (`repack-parent-mismatch`), and one whose prefixes do not reconstruct
this source with the SSE their entries state (`repack-source-mismatch`). Its receipt
names the parent with parent kind `re-pack`, and its other files are those of a pack
that codes. A verifier holding the parent checks the re-pack against it (`wai-pack
verify --parent`).
- **A floor that is the ladder's own CMAF segments** (§7.7). With
`floor.supplied_form = "cmaf"` (absent, the OBU form; existing policy digests are
unchanged), a supplied floor is an existing ladder's `init.mp4` and media segments, one
segment per piece, read box by box: the floor's temporal units are the samples, each
with a temporal delimiter before it. The package's floor files are the ladder's files,
byte for byte, named by their SHA-256, and the package carries the supplied floor's
listing (`floor.listing`), whose SHA-256 the policy pins, so the receipt binds every
byte of the ladder. A server can hold those files once for the classic site and the
package. New refusals: `floor-cmaf-malformed`, `floor-cmaf-timing`, `floor-cmaf-pieces`.
`wai-pack pack --link-supplied` writes them as hard links to the supplied files.
- **A floor-only profile** (§7.8). A component of `profile = "floor-only"` is its floor
rungs alone: no integer base, no refinement, no source (`policy-floor-only`
otherwise). Its pieces are its first floor rung's media segments (the CMAF form) or
shown key frames (the OBU form), each at most `max_ms` (`floor-piece-length`); each piece
is its floor objects, which present no base and so carry no `derived_from`, and its kind
is `none` (stage index kind 6). It attests delivery: the receipt binds every floor
object and the supplied floor's bytes, and the verifier holds them to the AV1 stream's
structure and the CMAF files; it states no decoded output, no fidelity to a source and
no primary. The stage index now requires a floor's `derived_from` exactly when its piece
has a base (`floor-derived-from`).
- **The independent verifier at scale** (`pack_verify.py`). Each object is hashed once,
and `http_verify.resolve_uri` finds an object by its content id rather than by hashing
every object for every URI. `--jobs N` runs every object's hash and every base's decode
and layers in N worker processes, piece by piece, ahead of checks that are unchanged in
order and reason. `--native-blake3` takes BLAKE3 from the `blake3` module after it
agrees with the pure implementation on the published vectors (opt-in: that module is
the implementation wai-rs links). `--package <dir>` verifies one package without its
sources or its key. **The measurement claim is checked** (§9.4): the claim corpus's
verifier reads it, and it is held to the receipt's figure, class, evidence and signer, so
a correctly metered package passes; `package-mutations/` gains a metered package (`ok`),
a claim about another figure (`measurement-claim-invalid`) and none
(`measurement-claim-missing`).
- **§7.3: key-frame sequence headers** (an informative note). A floor taken from an
existing ladder needs a sequence header in every key-frame unit; a producer that puts
one back must put back the stream's own first sequence header, byte for byte, not the
sample entry's `av1C` header, which can differ in its tool flags and is refused
(`floor-av1-malformed`). `tests/refusals.rs` holds the rule.
- **The DASH renderer** (`wai::http_binding::dash`) writes a representation's own
`@codecs`, `@width` and `@height` when given, and an adaptation set's `@codecs` only when
every representation shares it.
- **A streaming on-demand packer** (§11.4). The packer reads the source twice, a frame
at a time, once to cut it and once to code it, and each floor stream a temporal unit at
a time, and `wai-pack pack` writes each file into its directory as it is made, reading
objects back for the catalogs. What it holds no longer grows with the title, but for a
record per piece and per entry and a byte per frame per floor; a refusal removes what it
wrote. `--jobs <n>` codes n pieces at once, each alone, so the package is the same for
any n. A source that does not read the same twice is refused (`pack-source-changed`).
`tests/pack_alloc.rs` packs a source eight times as long in the same peak memory.
- **Conformance.** `packs/video-ladder-01/` (two floors, one at half size, over
`video-base-01`'s bases, which are the same objects), `ladder-mutations/`,
`packs/video-repack-01/` (the ladder re-packed from `video-base-01`),
`packs/video-cmaf-01/` and `cmaf-mutations/` (the ladder's streams as a muxer's fMP4
segments), `packs/video-floor-only-01/` and `-02/` (both forms, no base), index cases of
a piece of floors alone, `tests/ladder.rs`, `tests/repack.rs`, `tests/cmaf.rs`,
`tests/floor_only.rs`, and the independent
verifier `pack_verify.py`, with a box reader of its own, which re-derives every golden
from its sources and checks the re-pack against its parent. `supplied_floor.py` writes
one stream per floor rung; `supplied_cmaf.py` the CMAF form.
- **What a metered pack's figure brackets, and what a CMAF floor must agree with.** The
meter starts once the policy's own refusals have passed, before the source's first
read, so the figure covers the whole pack: both reads, the floor checks and a parent's
verification (§9.4). A CMAF floor's `av1C` and sample entry must be its sequence
header's, and every sample is a sync sample exactly when its unit starts at a shown key
frame (§7.7, §11.3). A re-pack's verification holds its parent whole (§11.4).
### Hybrid signatures: receipts that stay verifiable after Ed25519 can be forged
- **One suite** ([pq-signatures](extensions/pq-signatures.md) §2): ML-DSA-65 (FIPS 204) beside
Ed25519, as the composite `id-MLDSA65-Ed25519-SHA512` of
`draft-ietf-lamps-pq-composite-sigs-19`. Both halves sign the composite's message
representative of the record's own signed bytes, the ML-DSA half with the suite's label as its
context, so the Ed25519 half of a hybrid record verifies over no classical record's bytes and
stripping the post-quantum half leaves nothing that verifies (§4.3). ML-DSA-65 rather than -44
for the margin of security category 3 over the decades a provenance or delivery record is asked
to prove something.
- **Where it goes** (§6): a key-log entry may hold an ML-DSA key and a revision a hybrid root,
committed to before it signs by `BLAKE3("wai:key-set-next\x02" ‖ K ‖ P)`; a group receipt is
signed over the labelled payload's third version, `moq-jwp:group-sign\x03`, with the key in
its payload and both signatures in its canonical bytes and link hash, its objects unchanged; a
claim, and every JSON record signed as `wai:<domain>\x01 ‖ JCS`, carries `pq_suite`, `pq_key`
and `pq_sig`, read and checked by one shared rule (wired into synthesis's records).
- **Policy and downgrade** (§5, §6.1): a verifier may require hybrid (`hybrid_required`); at
confirmation, a classical-only item under a hybrid entry is refused (`hybrid_downgrade`), an
item whose ML-DSA key is not its entry's is refused (`pq_key_mismatch`), and under a pinned
anchor that requires hybrid only hybrid-signed revisions descend from it and a classical item
is unconfirmed. A bad post-quantum half is `pq_signature_invalid`.
- **Measured** (§9): a hybrid group receipt is 5552 bytes against 291, the 5261 bytes of the key
and signature paid once per group and never per object, 42 kbit/s on a receipts track at one
group a second; hybrid verification takes 114–128 µs against Ed25519's 34–35 µs.
- **Checked**: the ML-DSA-65 implementation against FIPS 204's ACVP vectors (keyGen, deterministic
sigGen and sigVer, vendored in `pq-conformance/acvp/` and pinned by SHA-256) and the composite
against the draft's own vector, before anything else; a corpus of 46 cases,
`pq-conformance/vectors.json` (`wai_pq_vectors`), and its second implementation,
`pq_verify.py`, which verifies ML-DSA from FIPS 204's text in pure Python. Classical publishers'
bytes and every existing corpus are unchanged; the reference reads the hybrid forms under the
feature `pq_signatures`, whose one new dependency is a pure-Rust ML-DSA implementation.
### `wai.splat.exact`: Gaussian splats in glTF as an exact WAI object, and frames that are the same everywhere
- **Carriage** ([splat-exact](extensions/splat-exact.md) §3): a `wai.splat.int_codec` object travels
in a glTF 2.0 asset as the draft compression extension `EXT_gaussian_splatting_compression_wai`,
nested in a mesh primitive's `KHR_gaussian_splatting` object. A buffer view holds the object's
`WAI1` envelope; the extension states its content hash and its parameter set (carried in the
asset, or held by digest) and, in another buffer view, the origin's commitment as its JCS text.
The primitive's five accessors carry no data, so both extensions are required, and `POSITION`'s
`min` and `max` are the decode's own. The draft JSON Schema is
`extensions/gltf/EXT_gaussian_splatting_compression_wai.schema.json`. WAI submits nothing to the
glTF registry; the extension name is registered here as a proposed wire identifier, of a new
kind, `gltf-extension`.
- **Attributes** (§4): the 14 planes of `wai.splat.int_codec` dequantized at 2^16 from the
payload's binary32 ranges, rounded half up exactly, and their mapping onto the base extension's
`POSITION`, `ROTATION`, `SCALE`, `OPACITY` and `SH_DEGREE_0_COEF_0`.
- **The renderer `wai-splat-render:i1`** (§5): a fixed-point camera and projection (the elliptical
weighted average model, its 0.3 px² low pass and 1.3× Jacobian clamp), culling with stated
rules, an order by squared camera distance with the index breaking every tie, per-pixel alpha
compositing with every rounding stated (`exp_neg_q16` of integer-operators §2, the 3σ cut-off,
alpha within 1/255 and 0.99, a stop below 0.0001), and 16 × 16 tiles whose order does not change
the frame. Every value compositing reads fits 32 bits, and only `e1` and `e2` need 64.
- **Commitment and view statement** (§6, §7): the origin signs the object, its set, its attribute
digest and the frame digest at each canonical pose; a sink signs, under a session key, the frame
it presented at a pose, canonical or its own.
- **Provenance** (§8): a view is a `convert` of the splat set under synthesis §3.2, so its class
is the greater of `reconstructed` and the splat set's. Synthesis origin claims may now name
`media` `volumetric` ([synthesis](extensions/synthesis.md) §3.1).
- **Quality cost, measured** (§12): against a floating-point renderer of the same model in `f64`,
59.4 to 67.2 dB PSNR on the corpus's views, no byte off by more than 4; the same float renderer
in `f32` gives other bytes than in `f64` on some views at the corpus's sizes and on nearly all
at four times the side.
- **Reference and corpus**: the `splat_exact` module of `wai-rs` (feature `splat_exact`), the
bins `wai_splat_exact_vectors gen|verify` and `wai_splat_exact_measure`, and the corpus
`live-conformance/splat/`: the learned cloud of `tests/vectors/int_splat/`, two authored scenes
carried through a one-layer identity set, and refused cases for every code of §10, among them a
reordered sort, a frame that does not recompute, a commitment over other splats and a pose
outside the pinned set claimed canonical. Its second implementation,
`live-conformance/splat_exact_verify.py`, decodes with the repository's other second
implementations, renders with its own integer renderer, and catches every mutated corpus copy,
every dropped check and every changed renderer rule.
- **On a GPU**: `wai-ops-gpu` gains the compositing kernel `wgsl/splat.wgsl`; `splat_exact::gpu`
(features `splat_exact`, `ops_gpu`) gives the CPU's frame on every view of the corpus on Metal,
and its test catches three edited kernels. `wai-ops-gpu.yml` runs it on the `macos` runner.
### `wai.media.robustness`: robustness evidence anyone recomputes, claims held to it, and soft bindings over a span of a track
- **Span bindings** ([jwp-receipts](extensions/jwp-receipts.md) §2.6.4): a claim of class
`soft-binding-span` declares a soft-binding value computed over a span of one track: its
media time in milliseconds, both ends included, and, where the publisher states them, the
groups or the objects that carry it. Its subject is the span's first object, the anchor. It
maps onto a block of a C2PA soft-binding assertion whose scope is a `timespan` (C2PA 2.4
§18.10); a whole-object binding is the block with the empty scope. A recovery through a span
needs a verified delivery receipt within the span and attests the span — not which part of it
the object in hand came from, nothing outside it, and, for a span of media time alone, no
object but the anchor; a receipt outside it is `recovery_outside_span`. A recovery record may
name a span declaration. New reasons `span_malformed` and `span_inverted` (step 4) and
`recovery_outside_span` (step 6). `soft-binding` claims are unchanged, and every existing
claim vector is byte-identical; the span vectors are appended to `claim-conformance/`, and
`claim_verify.py` and `differential.py` read the class.
- **Robustness records** ([robustness-evidence](extensions/robustness-evidence.md)): a corpus
document names pictures or clips by the SHA-256 of their samples, and itself by the SHA-256 of
its JCS. An attack battery is a list of registered transforms (`wai-attack:*`), each an integer
function of the frames, its exact parameters and a seed: synthesis §7.5.7's battery with its
parameters free, and a gamma computed in integers. A signed record names the algorithm by term
and definition digest, the corpus, the battery and the harness (`wai-robustness-harness:1`),
and per attack the counts — items that survived out of the corpus, items read as another
value, negatives read as marked — with survival, false-positive and balanced-accuracy rates and
the true-positive rate at 1% false positives as exact fractions in lowest terms, never
floating point (`rate_not_exact`). A verifier holding the corpus reruns the battery and the
detector and gets the table bit for bit (`table_irreproducible`); one that does not states the
record.
- **Claims held to their evidence.** A robustness claim lists the attacks an algorithm claims
to survive, by media, transform and parameters, and cites records; it holds only where a cited
record shows every corpus item survived the attack and no negative read as marked
(`claim_unevidenced`, `claim_exceeds_evidence`), and, under a policy that asks, only over
reproduced records (`evidence_not_reproduced`).
- **`wai-mark:tent64`'s own evidence.** Records over the `spectral`, `leaves` and `clips`
measurement sets with synthesis §7.5.7's battery, whose tables equal the synthesis corpus's
`battery.json`, and a claim that is exactly synthesis §7.5.8's list and holds over them.
- **The proposed `robustnessEvidence` object** (*Robustness Evidence for C2PA Soft-Binding
Algorithms*, pinned by digest) is mapped field for field, informatively (§11).
- `wai-rs` module `robustness` (feature `robustness`), `wai_robustness_vectors`, the corpus
`live-conformance/robustness/` (a conformance record over three pictures the corpus holds, the
tent64 records and claim, and a case refused for each reason), and `robustness_verify.py`, the
second implementation, which reruns the conformance battery end to end with `_wai_mark.py` and
requires the same table. Registry: `wai.media.robustness` (record), the document kinds
`wai-robustness`, `wai-robustness-claim` and `wai-robustness-corpus`, and the schemes
`wai-attack:` and `wai-robustness-harness:1`.
### Exact tone mapping: `wai.meta.gainmap`, `wai.meta.st2094_50` and `wai.media.tonemap`
- **Two companions** ([SPEC](SPEC.md) §5, §7.1): `wai.meta.gainmap` carries an ISO 21496-1:2025
gain map (its binary metadata as written, and its gain map image as a PNG of 8-bit samples, in
a `WGM1` frame) as a companion of an image payload; `wai.meta.st2094_50` carries SMPTE ST
2094-50:2026-08 (Application #5) metadata sets in their Annex C encoding, each with the frames
it applies to, in a `WD50` frame, as a companion of a video payload. Rule 2 of §7.1 gains the
`image` media class for the gain map. Both are classified `patent-pool` (royalty status not
established) and `standard-unclassified`. ISO 21496-1 was not read: its text is sold; what the
extension states of it is what ST 2094-50 says of it and what public readers of its binary
metadata implement, marked where it is used.
- **An exact rendition at a display's headroom** ([exact-tonemap](extensions/exact-tonemap.md)
§3 to §5): `wai-tonemap:gainmap` and `wai-tonemap:st2094-50` compute the rendition from the
base's `RGB8` samples, the companion and a headroom (an integer in 1/10000 of a stop) with a
fixed-point `log2` (16 bits of fraction, bitwise from below) and `exp2` (sixteen Q30
constants made by integer square roots), an sRGB table, and one rounding rule; the gain map's
upsampling is a pinned centre-aligned bilinear; ST 2094-50's slopes come from a Q62 series for
`tan` or from PCHIP on exact rationals; every step maps onto a formula of the standard. The
rendition is `RGB16`, linear light relative to the display's peak, so every sink at a headroom
presents the same bytes. Against a binary64 evaluation of the same formulas, over the corpus,
the largest difference is 2 code values of 65535 for each derivation (§6).
- **The rendition record** (§7, §8): a sink signs, for the renditions it presented, the base, the
companion, each headroom and each rendition's digest; a verifier holding the envelope, and the
frames for a video, recomputes them. A rendition is a `convert` of synthesis's lattice: its
class is the greatest of `reconstructed` and its inputs'.
- **Corpus** `live-conformance/tonemap/`, from generated HDR scenes rendered by the reference's own
integer encoder and the `int_video` clip; its second implementation, `tonemap_verify.py`, agrees
on every case. `png_still` reads and writes 8-bit greyscale; `staged_verify.py` knows the two
companions' base classes.
### `wai.telemetry.aggregate`: quality of experience the service receives only as sums
- **Measures, aggregated under DAP** ([telemetry-aggregation](extensions/telemetry-aggregation.md)
§3 to §5): a sink casts each session's metrics (delivery-telemetry §7.2) as four measures, the
start outcomes and the durations (Prio3SumVec), the stalls (Prio3Sum) and the video start time
(Prio3Histogram), and reports each through the Distributed Aggregation Protocol of
`draft-ietf-ppm-dap-20`: secret-shared between a leader and a helper aggregator, each share
encrypted to its aggregator with HPKE. The aggregators check each measurement's range proof
(`draft-irtf-cfrg-vdaf-22`) without either seeing it, refuse an out-of-range one
(`vdaf_verify_error`), and neither learns a measurement as long as one of them follows the
protocol. Leader and helper are roles any party can run.
- **The task binds who aggregates.** A task's `task_info` names its measure, its scope (zero, or a
room id), a salt and the keys of the two aggregators and the collector; it enters the
authenticated data of every input share, so a report is bound at upload. The task id is SHA-256
over the configuration. A task's minimum batch size is at least fifty, delivery-telemetry §8.4's
floor, and each aggregator refuses to release a share of a smaller batch (`invalidBatchSize`):
the floor is enforced by the aggregators, not by the service's restraint.
- **Signed aggregates.** Each aggregator signs a statement of its aggregate share with the batch's
task, collection, interval, report count and checksum (`WQS1`), sealed to the collector; the
collector's aggregate receipt (`WQA1`) carries the task, both statements and the result. A reader
recomputes the result from the two shares and refuses a receipt with one aggregator, a batch below
the minimum, statements that disagree, or a batch whose reports were uploaded to another task
(§6 to §8; the reasons are §11's).
- **Where aggregation replaces suppression** (§9): for a figure a measure aggregates, the published
figure is the receipt's; delivery-telemetry §8.4 still applies to Request-mode reports, to
Event-mode reports consumed as they arrive, and to figures no measure aggregates. A room's
quality-of-experience cell ([live-metering](extensions/live-metering.md) §4.7, §6.5) is computed
from its `outcomes`, `stalls` and `durations` receipts, and equals the cell its logs give.
- **Key classes** ([delivery-telemetry](extensions/delivery-telemetry.md) §3.4, §4.3): every CMCD key
the mapping writes is `delivery` (what the serving host needs to serve the request) or
`measurement` (what is only aggregated afterwards), each with what it reveals and to whom; a
delivery-only Request mode carries the delivery class alone, and a reader refuses a
delivery-only report with any other key (`measurement_key_in_delivery`). §8.1 now states facts
and sets no consent requirement for the service's own mechanics; a lawful basis, where a regime
asks for one, is a `lawful-basis` claim (jwp-receipts §2.4.6).
- **Reference implementation** (`wai-rs`, feature `telemetry_aggregate`): Prio3Count, Prio3Sum,
Prio3SumVec and Prio3Histogram written from the VDAF draft (the fields, TurboSHAKE128 and its XOF,
the FLP in the Lagrange basis) and passing the draft's published test vectors, vendored under
`live-conformance/aggregate/vdaf-22/`; HPKE base mode (RFC 9180 A.1.1); DAP-20's messages and a
reference client, leader, helper and collector; the receipt verifier and the cell. New
dependencies, behind the feature: `keccak`, `aes-gcm`, `hkdf` (RustCrypto).
- **Corpus** `live-conformance/aggregate/` (`wai_telemetry_aggregate_vectors gen`), with an
independent verifier, `telemetry_aggregate_verify.py`, that evaluates Prio3, HPKE and DAP itself
and re-runs every flow from its reports byte for byte.
### `wai.audio.opus.dred` and `wai.audio.opus.dred.int`: Opus deep redundancy, decoded exactly
- **Registered** ([opus-dred](extensions/opus-dred.md), SPEC §5): the deep redundancy (DRED) of
draft-ietf-mlcodec-opus-dred-07, a neural latent of the recent speech carried in an Opus
packet's padding, from which a receiver rebuilds the features of lost packets. Both strings are
companion-only enhancement layers over `wai.audio.opus.packet`, carried embedded in the packet's
padding or as a `WAI2` companion; not refinements, since DRED corrects nothing the base decoded
and is read only where the base is missing (§2.2). `wai.audio.opus.dred` is the draft's decoding
within its §4.1 tolerances (`StandardTolerance`); `wai.audio.opus.dred.int` is the integer profile
(`NeuralIntegerExact`, one-file parameter set, `ops` lowering `dred-int/1`). Both `patent-pool`,
conservatively, while the royalty-free intent of the draft's IPR disclosure is conditional. The
working group's other documents are placed (§2.3): the extension format and the speech coding
enhancement register nothing, the scalable quality extension rides embedded in the base.
- **The integer profile `dred-int/1`** (§3–§4): DRED found in a packet (RFC 6716's framing, the
extension draft's iterator with its repeat mechanism), the payload's integers (RFC 6716's range
decoder, with a magnitude cap that bounds crafted payloads), and the draft's own decoder network
from its own weights file, pinned by SHA-256 and never committed, lowered to two `wai-ops/1`
graphs: weights at `2^24` from their float32 bits, activations at `2^15`, `tanh` and the sigmoid as
`lut16` tables of a stated integer computation, every layer a `conv`, `mul`, `add` or `rescale`
with its rounding stated. Both graphs meet `acc64` and use only `wai-ops/1-i32` operators.
- **Measured** (§6), on the draft's test vectors: the profile passes the draft's feature test
against its reference features on every vector, and lies at `total` 0.0021 (largest difference
0.0084) from the network in floating point. The reference features are not the network's: two
builds of the draft's reference decoder on one host give features about 0.13 apart, neither the
test vectors'.
- **A stream under loss** (§5): a seeded loss pattern, the slots each recovering packet's DRED
rebuilds, a reconstruction buffer and its digest, and the record a verifier checks
(`weights_digest_mismatch`, `loss_pattern_mismatch`, `reconstruction_mismatch`).
- **Receipts** (§7): a reconstruction from DRED is a `convert`; concealment beyond it a `generate`;
speech coding enhancement an `assist`. A player presents a reconstruction in synthesis's log, its
`output` the run's digest at `decode-equivalence` (`present_output_irreproducible` when it does
not recompute).
- **Reference and corpus**: the `dred_exact` module of wai-rs (feature `dred_exact`), the corpus
`live-conformance/dred/` (payloads, streams, cases, negative controls for every code: another
weights file, a loss list that is not the pattern, a moved row, a flipped digest, a
floating-point decode claimed and presented as exact, a broken log signature), its second
implementation `dred_verify.py`, the measurement test, and a GPU test that holds the device's
integers to the CPU's. The profile as a proposal to the working group is drafted, not filed, in
`proposals/dred-integer-profile.md`.
### `wai-mark:tent64s`: the second marking layer found again after crops, rotations and captures of a screen
- **The scheme** ([synthesis](extensions/synthesis.md) §7.9), a second registered term beside
`wai-mark:tent64`: the same tent pattern, code, amplitudes and margin, on a tiled layout. The
64 × 64 grid is cut into 2 × 2 tiles of 32 × 32 cells, each holding the same layout, so a bit
keeps 16 cells, four in every tile, and the mark repeats with half the frame's width and height.
The embedding is §7.5.4's under that layout, integer and recomputed by a verifier as `tent64`'s
is; nothing is added for synchronisation. A new term rather than a revision: the same frames and
payload give other marked frames, and a `tent64` marking commits to its own by digest.
- **Detection in two stages, integer throughout.** Stage 1 reads the grid in place, in the 8
quarter turns and mirrors. Stage 2, where stage 1 reads nothing: the exact autocorrelation of a
sign plane matched to the cell pattern; eight lattice candidates, each a pair of vectors at
right angles scored over four lattice points and refined to 1/64 of a sample; each folded onto
one tile, the cells' phase taken where the tiles agree most; the orientation and the tile's
offset where the four cells of every bit agree most; at most four words decoded. The
false-positive bound counts all 12 decodes, 2^−60.80, and is checked against the words both
stages read from unmarked pictures.
- **Measured.** The battery adds a crop of 25%, crops of 2%, 5%, 10% and 25% from each side, a
rotation of 5° and two simulated captures of a screen (a resampling, a small rotation, a
display's curve and noise). Both generated sets survive every attack of the table, resampling to
1/3 among them; the claims are what the photographs survived as well, among them every attack
`tent64` claims, crops of 1% to 5%, crops of 2% to 10% from the left, rotations of 0.5°, 1° and 5°
and one capture of a screen. One photograph of dense texture is not read in 14 of the crops and
rotations, nor another in the second capture, and those are not claimed. PSNR, SSIM and the floor
are `tent64`'s to within 0.4 dB and 0.002.
- **Verification**: §7.6 recomputes and reruns a marking or a detection of either registered
scheme; the C2PA statement, the signpost and the resolver of §7.7 serve both.
- **Reference and corpus**: `synthesis::mark::sync`; its files sit in
[`live-conformance/synthesis-mark/`](live-conformance/synthesis-mark/) beside `tent64`'s and share
its frames, and a vector names a crop, a rotation or a capture of stored frames, which every
implementation derives, rather than storing it. `synthesis_mark_verify.py` runs both stages
with an autocorrelation and a fold of its own and agrees with the reference on every vector,
stage 2's lattice, phase, offset and agreement among them.
- **`scripts/check-public-names.sh`** reads file arguments from the caller's directory, where it
read them from the repository's root, and names any argument it cannot read rather than exiting 2
on the whole list; its self-test covers both.
### `wai.live.coperformance`: a relay counts audience inputs in their chain's order
- **The rule** ([co-performance](extensions/co-performance.md) §7.3): a relay counts an
author's inputs in the order of the author's chain, as a verifier does, never in the order
they reach it. The records of one chain can reach a relay on several streams, one for each
group, read side by side, so a member's second input could be taken before its first, and a
relay counting as they came could forward the second and refuse the first, where a verifier
accepts the first and refuses the second, leaving a gap in the member's chain where the first
input was. A relay now decides an input once it has received every record of the chain below
it and decided every input among them; it holds an input that arrives ahead of a gap, for a
bound of its own, and refuses one whose gap has not filled by then as `input_out_of_order`,
counting the chain from the record after it; and it refuses as `input_out_of_order` an input
that arrives after a later input of its author was forwarded. The reason is the relay's
alone, as `window_committed` is (§8): a verifier holds each chain in order, and `wai-rs` and
`co_performance_verify.py` already count each input where the merged order meets it, so
neither changes and the corpus gains no vector.
- **The relay** (`joule-moq-relay`) keeps each author's chain as it has received it, holds an
input ahead of a gap with the rest of its stream until the gap fills or `[floor] hold_ms`
(2 000 by default) runs out, and counts `input_out_of_order` in the session's summary.
`tests/floor.rs`: a member's second vote, sent first on a stream of its own and held, then its
first: the first forwarded, the second refused `input_over_limit`; and a second vote whose first
never comes, refused `input_out_of_order` no sooner than the hold, after which the first, sent
late, is forwarded.
### `wai.age.assurance`: the room's own service under the serving rule
- **The rule** ([age-assurance](extensions/age-assurance.md) §5.5): a session that proves
possession of a room key, the authority's or the clock's, is the room's own service, and a
relay serves it every object of the room's tracks whatever their age. A room that admits by
age and keeps co-performance needs it: the clock follows and logs every performer's objects
over a session of its own (co-performance §8), which holds no grant and no token. The proof
is a `GRANT_PROOF` naming no grant (32 bytes of zero) over the session's challenge, checked as
live-rooms §10.5.4 checks a proof; one that does not pass refuses the session with that
section's reason, and one by a key the relay does not hold as a room key gives nothing. No
name, token or flag a client sets makes a session the room's.
- **The relay** (`joule-moq-relay`) checks such proofs in SETUP and exempts the session in the
rooms of each room key it proved: an authority it honours for the room, or a room key its
`[floor]` or `[dvr]` names. Its client presents them (`ClientOptions::room_keys`), and the
room service's clock proves both room keys it holds. `tests/age_room_service.rs`: in a room
rated 18 with the floor held, sessions proving the clock's key and the authority's receive
every group; the clock's token with no proof, and a proof by a key that is no room key, are
refused `below_threshold`; a proof naming the clock's key under another key's signature, and
the clock's proof over another session's challenge, close the session `UNAUTHORIZED`.
- **The corpus** gains the serving case `room_service` (the two room keys served at threshold
0; sessions with no proof or a non-room key served by their thresholds; a forged signature,
another session's challenge, another relay, a named grant and a cut proof refused with
live-rooms §10.5.4's reasons at any threshold). `wai-rs` (`age_assurance::room_service`) and
`age_assurance_verify.py` agree on every request; the verifier drops each of its three new
checks in turn and four new mutated copies of the corpus, all caught.
### `wai.live.cowatch`: watching together in a room, with the sync measured
- **The extension** ([co-watch](extensions/co-watch.md)) registers a record for a room that
watches one source together. The room authority signs a co-watch policy, `WXP1` (§3): the
source (a WAI package by its receipt, a sequence of on-demand objects by a digest of their
content hashes and durations, or a recording by its manifest), its duration, and the bounds of
every rule below. A policy is replaced by one chained to it; one naming another source starts
the controlled position again.
- **Controls** (§5): `play`, `pause`, `seek` and `rate`, `WXC1`, as `meta` events, each effective
at a room-clock instant ahead of its sending by at least the policy's lead and at most its
horizon, so every viewer applies it at that instant whatever its own latency. Who may control
is the direct power or the grant attribute `watch_control`, one bit per action, which needs no
new power bit. Controls apply in a pinned order, by instant, then by sending, then by digest:
of two for one instant that set the same thing, the one sent later prevails, and the other is
stated overridden (§5.4). The controls define the controlled position at every instant (§6).
- **Presentation statements** (§7): a viewer's player signs, under its own key and in its own
chain, the source position it presented at a room-clock instant, its state and its rate, at
least once a slot. A statement lifted from another chain, signed by another key, about an
instant after its sending, or naming another source is refused.
- **Skew** (§8): the room clock's skew statement, `WXK1`, states per window, for each viewer, the
slots due while it was present, the samples it has and those counted (a sample within the
policy's settle time of a join, a control or a source's start is not), and the 50th
percentile, the policy's percentile and the greatest of its absolute errors, with a verdict
against the bound; and the same for each pair of viewers. `held` pins the statements the clock
had received, so one arriving later changes nothing. A verifier recomputes every entry; a
viewer with a due slot of no statement is unknown, never in sync; and a window's statements
together list every viewer of it.
- **Catch-up** (§9): a pinned correction from the controlled state and the player's position:
the controlled rate within the tolerance, a rate nudged toward the room and held within the
policy's limits up to the seek threshold, a seek past it, and a seek or a hold while paused.
- **Reference**: the `co_watch` module of `wai-rs` (feature `co_watch`); the corpus
`live-conformance/cowatch/` (`wai_co_watch_vectors gen`): a watch party with a conflict at one
instant and a late joiner, a room whose second policy starts another source, a room with a
viewer out of sync, one unknown, one that leaves and a window stated in two statements, a gap,
refused vectors for every reason of §12, and catch-up cases; its second implementation
`live-conformance/co_watch_verify.py`, which recomputes every entry, requires every mutated
copy to fail and catches each of its own checks dropped.
- **The room service and the page** (`joule-moq-room`, `joule-moq-web`): a co-watch room serves
its source and carries its viewers' controls and statements in a log, the clock signing each
viewer's presence and each window's skew statements; the room page has a co-watch mode with
shared controls and a sync indicator, and the module `web/room/cowatch.js` gives every
catch-up case of the corpus its correction and writes its controls and statements byte for
byte.
### `wai.media.upscale`: super-resolution as an exact derivation, residuals against it, and an account of sink-made pictures
- **The extension** ([exact-upscale](extensions/exact-upscale.md)) registers the record
`wai.media.upscale` and two integer derivations, `wai-upscale:sr2` and `wai-upscale:sr4`: the
pinned upscale of integer-operators §5E E11 plus a detail an integer network computes from the
picture, clamped to the sample range. The network is a `WSR1` prior (§3.1), one file a sink
holds by its SHA-256 under SPEC §3.1: up to eight convolutions of signed-byte weights, each with
a per-channel multiplier, a shift and `half_up` rounding, the hidden ones clamped, the last one's
twelve channels shuffled to the two sub-positions of each colour. Colour is `RGB8`, the channels
together; the base repeats the last row and column, and the network reads mid-grey outside the
picture; `sr4` is `sr2` twice under one prior.
- **Through the integer operator set.** The lowering `sr2/1` (§3.4; integer-operators §13.7)
writes one `WHG1` graph of `rescale`, `conv`, `clamp`, `pixel_shuffle` and `add`, which loads under
`acc64` with its bounds certificate, and under `acc32` where the certificate meets it: every
operator is in `wai-ops/1-i32`, and the GPU path runs it. Every sink presents the same bytes,
so the high-resolution picture is a function of the transmitted bytes and the prior.
- **Residual against the known upscale.** An origin can send a `WIR1` payload of staged-delivery
Appendix A with the identity predictor, decoded over the upscaled picture, and sign an
**upscale record** (`wai-upscale`, §4.2) stating the input, the prior, the upscale, the output
and the target with the bound within which the output lies: 0 for the target itself, ⌊q/2⌋ for
a residual of step q.
- **Per-title priors** (§5): a prior fine-tuned on one title, carried by digest like any other,
and sent only where the bytes it saves exceed its own. `tools/wai_int_sr_train.py` trains priors
in floating point from fixed seeds and quantises them to `WSR1`; its training is not claimed to
regenerate the same bytes on another machine, and the priors' bytes are what is pinned.
`wai_upscale_measure` codes the full-resolution picture directly with `WIV1`, through staged
delivery's up2 layer, and through the derivation and a residual, at matched quality (every
sample within ⌊q/2⌋), on the pictures the tool generates.
- **Accounting for sink-made pictures** (§6). A sink's **presentation statement**
(`wai-presentation`) lists, for a span, each picture it presented as `transmitted` (decoded, or
refined by a residual to the origin's target), `derived` (an exact derivation with no residual),
`asserted` (an edit no one can recompute, such as a float upscale) or `synthesized` (generated,
such as a frame between two others), with its pixels counted by category and the span's totals.
Each picture is held to its origin claim: an upscale claimed captured, or a generated frame
claimed assistive, is refused (`made_understated`). A generated picture names the model that
made it, in the slot `made_by` of the contribution record `wai.media.contribution`.
- **Synthesis** ([synthesis](extensions/synthesis.md)): an origin claim's subject may be a
picture by the digest of its samples, for output a sink presents and does not publish; replicate
evidence may name `wai-upscale:sr2` or `wai-upscale:sr4` with `{"prior"}`, proved by
recomputation from the input's samples and the prior a verifier holds (§4.3, §4.4, §11.1); and a
pinned integer derivation over the decode keeps a presentation path's tier (§9.2;
determinism-tiers §2). Its corpus gains a sink's upscale proved, one whose recomputation differs,
one under a prior that does not read, and one claimed about other samples.
- **Reference and corpus.** `wai-rs` module `int_sr` (feature `neural_int`), module
`exact_upscale` (feature `exact_upscale`), and the GPU test `tests/exact_upscale_gpu.rs`
(features `ops_gpu`, `exact_upscale`). `live-conformance/upscale/`, written from fixed seeds by
`wai_upscale_vectors` around two trained priors pinned by SHA-256: upscales under the generic,
a seeded and a per-title prior, residuals exact and to a bound, a span of four kinds of
picture, and every reason of §8 refused. Checked by `tests/exact_upscale.rs` and by
`live-conformance/upscale_verify.py`, a second implementation that shares no code with `wai-rs`,
runs the network with `live-conformance/_int_sr.py`, and requires every mutated copy of the
corpus to fail. The registry lists the record, the magic `WSR1` and the document kinds
`wai-upscale` and `wai-presentation`.
### `wai-mark:tent64`: the second marking layer, a watermark recomputed from its payload and read with no key
- **The scheme** ([synthesis](extensions/synthesis.md) §7.4, §7.5) fills the scheme slot of §7.4: a
mark in the luma of RGB8 pictures and video frames, registered by a term and the SHA-256 of its
definition. Each frame is cut into a 64 × 64 grid that follows the picture through a rescale; a
separable tent checkerboard in each cell is zero-mean, so brightness leaves a cell's statistic
unchanged and contrast scales it. A public SHA-256 stream shuffles the cells, signs them and
masks the code; the 8-byte payload is a BCH(255, 71) codeword shortened to 248 bits, each bit
carried by 16 cells scattered over the frame. Each bit gets the least amplitude, up to 12 luma
levels, that gives its statistic the sign the codeword asks with a margin of 3/2 of its weight
mass, so a frame is changed only as far as the margin asks. A video carries the whole payload
in every frame.
- **No other channel.** The embedding is a pinned integer derivation of the unmarked frames and
the payload: a verifier holding the unmarked frames recomputes the marked ones
(`mark_recompute_mismatch`), so the mark carries exactly its payload, a function of the claim,
and no viewer or session identifier. A sink that generates content marks it as part of the
reconstruction its tier holds (§7.8). The marking record gains `embedding` (the frames' size
and the digests of the unmarked and the marked frames).
- **Key-free detection** in the 8 quarter turns and mirrors, decoding within 29 bits; a
false-positive bound of 2^−61.39 derived from the code and checked against the words read from
unmarked pictures, which match the uniform model. The detection record gains `read` (the
frames it read), and a verifier that holds them runs the detector again
(`detection_irreproducible`). A conversion that does not mark again carries its input's mark,
and its detection agrees by inheritance (`agree-inherited`).
- **Robustness measured, not asserted.** An attack battery of integer attacks (re-encoding with
the crate's own `wai.video.int_motion` encoder and a staged base, the pinned derivations of §4,
area resampling, crops, quarter turns and mirrors, small rotations, noise from a fixed stream,
brightness, contrast, a local blur; for clips, inter coding and dropped frames), over
measurement sets the crate generates, gives the table of §7.5.8; the scheme claims to survive
only what every set and a set of photographs survived. It claims no crop and no small rotation.
PSNR and SSIM per picture, and a PSNR floor that depends on the frame's size alone.
- **Signpost and resolver** (§7.7): the marking's C2PA statement (`c2pa.watermarked.bound` and a
`c2pa.soft-binding` assertion naming the scheme and its digest), the openly disclosed mark
itself, and `/.well-known/wai-mark/<name>`, proposed, for the definition and for claims by
payload.
- **New codes**: `scheme_mismatch`, `mark_evidence_invalid`, `mark_input_mismatch`,
`mark_recompute_mismatch`, `detection_irreproducible`, `layer_not_proved`; the policy member
`second_layer`. `wai-mark:conformance` stays, as the scheme with no algorithm.
- **Reference and corpus**: `synthesis::mark` (feature `synthesis`) and `synthesis_mark` (feature
`synthesis_mark`), with `wai_synthesis_mark_vectors gen|verify|measure`;
[`live-conformance/synthesis-mark/`](live-conformance/synthesis-mark/), and its second
implementation `synthesis_mark_verify.py` with `_wai_mark.py`, which mutates the corpus and
weakens its own verifier in turn. Audio is not reached in this revision.
### `wai.media.exploration`: archives that replay, default-distance, a homogenization meter, and taste as a declared, owned objective
- **The extension** ([exploration](extensions/exploration.md)) registers the record
`wai.media.exploration`. It builds on the exactness of `wai.generate.spectral`: what elsewhere
can only be estimated about a generator's outputs is recomputed here.
- **Descriptors.** Six integer derivations over a picture's `RGB8` samples, each from 0 to 1024:
mean luma, chroma, detail (the mean luma step between neighbours), palette diversity (the
Gini–Simpson index of a 64-colour quantisation) and the two coordinates of the luma centroid.
No learned model: they place a picture and judge nothing. A grid of one to four axes bins them
into niches.
- **Archives.** A generation request may ask for an archive instead of one picture:
MAP-Elites over a declared grid, through the prompt and up to eight modifiers the person
declares, run by the pinned schedule `wai-exploration:map-elites/1` from a declared search
seed. Each elite names its genome (seed, steps, modifier mask) and regenerates; the archive
replays bit for bit, so an elite dropped or chosen after the fact is refused
(`archive_replay_mismatch`). The placement rule is `none` (the first trial to reach a niche
keeps it) or a taste prior.
- **Default-distance.** A prompt's default set is seeds 0 to 15 at four steps. A work's distance
from it, in descriptors and in samples (0 exactly when the work is a default), is a signed
`measured` figure for the exact generator, an `estimate` for any other, and `unknown` with
every figure `null`, never 0.
- **Collection statements.** The descriptor coverage of a grid and the distribution of pairwise
descriptor distances of a gallery, a room's outputs, a session's candidates or an archive,
recomputed from the samples.
- **Taste priors.** An integer ranking model over the descriptors (`rise`, `fall`, `near` and
`far` terms, unsigned throughout), a one-file parameter set pinned as SPEC §3.1 pins one,
signed by its owner (`taste_not_owner` otherwise) and carrying the owner's `wai.ai.usage`
statement; a ranking under it reproduces, and states the use it made of the prior, which the
owner's statement may disallow. Personal and community priors are the same kind of object, and
none is a default.
- **Disclosure.** Every archive, ranking and pick that an aesthetic or reward model steered names
it by digest or states `none`. A missing disclosure is reported undisclosed, never `none`; a
policy may require it (`objective_undisclosed`), and a pick from an archive a prior steered must
name that prior (`objective_mismatch`). A pick from an archive inherits the elite's genome: the
slot the contribution record fills to point a `candidates` step at an archive.
- **Reference and corpus.** `wai-rs` feature `exploration` (`src/exploration.rs`), and
`live-conformance/exploration/`, written from fixed seeds by `wai_exploration_vectors gen`:
generations that check the generator's restatement, descriptor vectors, archives, distances,
collections, rankings and picks, accepted and refused at each rule. Checked by
`tests/exploration.rs` and by `live-conformance/exploration_verify.py`, a second implementation
that shares no code with `wai-rs`, implements the generator from the extension's Appendix A,
requires every mutated copy of the corpus to fail and every weakened copy of itself to be
caught. `wai_exploration_vectors measure` runs an archive against plain seed sampling and
against uniform sampling of the same genome space at equal compute. The three priors of the
corpus are written in code with hand-set weights; nothing is trained on anyone's choices.
### Privacy Pass admission end to end: one scope per room, admission by the room's service over HTTP, ratings over MOQT, and the serving rule at the relay
- **One scope per room** ([live-rooms](extensions/live-rooms.md) §10.4). The subscribing scopes
§10.4 named did not fit the 255 bytes `draft-ietf-moq-privacy-pass-auth-03` gives the scopes of
one MoQAuthorizationInfo: each scope restates its namespace match, 69 bytes for the room id, so
three fit and §10.2 step 5's four at the least (320 bytes) do not. A room's scope is now one
MoQAuthScope, the actions SUBSCRIBE_NAMESPACE, SUBSCRIBE, FETCH and TRACK_STATUS on
`MATCH_PREFIX` of the room id's tuple and on every track name, 78 bytes; what it does not carry
is applied from the room's own records. A verifier reads a challenge's `origin_info` by the
draft's grammar and refuses one that does not read `scope_malformed`, and one that reads and is
not the room's scope `scope_mismatch`, the previous revision's encoding by these names
([age-assurance](extensions/age-assurance.md) §5.2 step 2, §11).
- **Redemption contexts per window** (age-assurance §5.1, §5.3). A challenger may fix a
challenge's context for a room and a window of time, accepting the current window's and the
one before, so a token may be obtained before it is presented; it keeps the nonces of the two
windows it accepts and drops a window's when it stops accepting it, bounds them per window, and
past the bound redeems no further token rather than forget one.
- **Admission by the room's service over HTTP** (age-assurance §5.6). A room's service is a
Privacy Pass origin of RFC 9577: it answers a grant request without a token `401` with a
`PrivateToken` challenge for each key of the room's policy, its `origin_info` RFC 9577's list
of server names and its context the room's window; checks a token by §5.2 against the room's
age; and states the threshold in the grant's `age` (§5.4). Over HTTP a challenge's
`origin_info` must list the service's own name.
- **Ratings over MOQT** (age-assurance §5.7). A rating claim rides as the property `WAI_RATING`
(`0x3A4D`), a Track Property of a PUBLISH or SUBSCRIBE_OK and an Object Property of any object;
a relay holds what §3.6 lets it honour from the instant it reads it.
- **The corpus** (`live-conformance/age/`) gains the scope cases (the previous revision's three
and four scopes, another room's, an empty one, an action outside the draft's table, a byte
after the scope, a list of server names over MOQT) and the HTTP binding's (admitted under the
service's name and under a list holding it; refused under another origin, an empty list and
the MOQT scope), and RFC 9578 Appendix A.2's issuance (its nonce, blind, salt, TokenRequest
and TokenResponse), which both verifiers check: the blinded message is (*s*·*r*)^e mod *n*
and the response *s*·*r* mod *n*.
- **The reference stack.** `joule-moq` reads and writes rating claims as `wai-rs` does, and
agrees with the corpus on every claim and serving request. `joule-moq-relay` holds the claims
it is carried, bounded per room, per track and in all, and serves each session by the
thresholds of the grants its SETUP proved: a live object of a group above it is not
forwarded, the streams of groups a newly held claim raises are ended, and a SUBSCRIBE, a FETCH
or a fill is refused `below_threshold` at its start or answered up to the first refused group.
`joule-moq-room` (`[age]`) is the HTTP origin of §5.6 with `wai-rs`'s checks, keeps its nonces
per window, counts admissions by window, issuer and threshold alone, and signs
`wai-age-admissions` receipts with the room's clock (`GET /age/receipt`, and `receipts_out`
as each window closes). The room page answers the challenges with a token it obtains by blind
issuance (RFC 9578 §6, RFC 9474 §4.2 to §4.4) from the issuer's directory (RFC 9578 §4),
carries the room's rating on every track it publishes and, with `rate`, its own rating of its
video, and shows what the relay withholds. Its end-to-end scenario `age` runs it all against a
test issuer over the corpus's published keys.
### `wai.age.assurance`: content ratings carried through derivations, admission by an age threshold without identity, and the minors' profile
- **The extension** ([age-assurance](extensions/age-assurance.md)) registers the record
`wai.age.assurance` and three JSON records signed as synthesis' are: a rating claim
(`wai-rating`), an age policy (`wai-age-policy`) and an admission receipt
(`wai-age-admissions`).
- **Rating claims.** A claim rates an object, a track from a group on, a span of a track or a
room under one to eight schemes, each named by its vocabulary URI, with the value it gives and
the minimum age that value means; several national schemes sit side by side and WAI adds none.
An object's age is the greatest age of the claims that cover it, so a claim only raises, and a
span rated after its first group went out holds from that group whenever the claim arrives
(§3.5): a relay serves none of the span below its age from then on, live, by FETCH, from the
cache or from the DVR window. A derived object's claim names its inputs' claims by link hash,
and a derivation that names no claim for a rated input, or drops or lowers a scheme an input
is rated under, is refused `rating_dropped` (§3.4), as synthesis §3.4 carries an origin class.
A room's tracks are rated by its room keys and each track by its owner, and a claim another key
signs is refused `rating_not_authorised`.
- **Admission by threshold.** A room's age policy, signed by its authority, names Privacy Pass
issuers (RFC 9576, RFC 9577, RFC 9578) with one key per threshold, from 1 to 21, so a token of
the publicly verifiable type 0x0002 says "at or over *T*" and nothing else. A relay checks a
token in six steps (§5.2): it reads, its challenge is one the relay sent the session, its key
is the named issuer's, its RSASSA-PSS verifies, its nonce was not redeemed, and its key's
threshold is at least the track's age; it learns the issuer and the threshold alone. A grant
states the threshold its authority admitted the principal under in the attribute `age`
(live-rooms §4.4).
- **No return path.** On a one-way bearer a rating is a sink-evaluated predicate: the device's age
state is the greatest threshold of a credential it holds under an anchor provisioned on it,
each credential a token issued for the issuer's device challenge, and a player presents an
object only at or above its age, evaluates again as claims arrive, and sends nothing (§6).
- **Evidence without identity.** An admission receipt, signed by a room key, counts admissions
per window, issuer and threshold; a cell under 50 is suppressed, as live-metering's QoE cell is
under delivery-telemetry §8 (its paragraph 8.4); and a member beyond its table, at its top or in a cell, is
refused `receipt_identifies`, whatever it holds. Given the admissions, a verifier recomputes the
cells. Where a regime asks the operator for a lawful basis, the operator states it in a
`lawful-basis` declaration (jwp-receipts §2.4.6) about the receipt.
- **The minors' profile.** A room in which a grant that may publish and carries `minor` = `true`
takes effect must meet the profile, named from bitmaps it already has: recording `mode` 0, no
role with the record or the seek power, no role that publishes nothing permitting `chat` or
`reaction`, and no grant that publishes nothing permitting them either; otherwise
`minor_profile_unmet`. WAI defines no gift; a clip needs a recording or a DVR window, and the
room has neither.
- **live-recording** §4.4 step 5 refuses a package of a room whose recording policy has `mode` 0
(`recording_not_permitted`, capture-binding's code), takes or no takes; its corpus gains
`room_never_recorded`, and `live_record_verify.py` the check.
- **The relay** (`joule-moq-relay`) keeps no DVR window of a room once a session proves a minor's
publishing grant of it: what it held is deleted, nothing more is written, and the room is
remembered across a restart, as a room whose objects are sealed is; `joule-moq`'s grant head
reads the `minor` attribute.
- **Conformance.** `wai-rs` gains the `age_assurance` module (feature `age_assurance`), reusing the
ad-presentation module's RSASSA-PSS and token reader; `live-conformance/age/` is written by
`wai_age_assurance_vectors` from fixed seeds, its issuer keys the published test keys of RFC
9578 Appendix A.2, RFC 9500 §2.1 and RFC 7517 Appendix A.2, with RFC 9578's first token vector;
and `live-conformance/age_assurance_verify.py`, written from the text, is its second
implementation.
### `wai.live.ad_presentation`: a player's signed claim that an ad was presented, and a spend that counts only presented impressions
- **The extension** ([ad-presentation](extensions/ad-presentation.md)) registers the record
`wai.live.ad_presentation`. A presentation claim, `WPC1`, is a player's signed statement
about one impression of a live-ads break: the break, the session and the slot of its
form-2 `wai.video.ssai` receipt; the content hashes of the creative's media objects it
presented; spans of the creative's media time, each with the least fraction of the pixels it
observed in view, in basis points, and whether the ad was audible, measured over the ad's
pixels, the player's area (the disclosure the video viewability guidelines ask for) or a
television's full screen; a count of rendered frames where the platform exposes one; and
optionally a device attestation.
- **The session key.** A player makes an Ed25519 key for each playback session of a room and
signs its claims with it; the session its receipts name is derived from the key and the
room, so the decider never chooses it and it links nothing across rooms.
- **The creative digest.** A creative is its media objects in order; its hash, in the pod,
the receipt and the asset list's URIs, digests their content hashes and durations, so a
claim's presented objects are checked against the bytes the creative's URI names.
- **Viewability rules, as their texts state them.** The video rule: at least 50% of the ad's
pixels for 2 continuous seconds of played, unduplicated content (*Viewable Ad Impression
Measurement Guidelines (Desktop)*, Version 2.0). The cross-media rule: 100% of the pixels for
2 continuous seconds, which on a television output may be assumed (*Cross-Media Audience
Measurement Standards (Phase I Video)* §2.2.1). A pause, stall or skip ends a run. The
quartile a claim reaches is its contiguous coverage at VAST 4.3 §3.14.1's marks, and must be
the receipt's `quartiles_reached`.
- **Device attestation.** A Privacy Pass token of RFC 9577 §2.2.1 whose TokenChallenge has as
its redemption context a digest of the impression and the session key, so one token answers
one impression; type 0x0002 is verified from the issuer's key as RFC 9578 §6.4 does
(RSASSA-PSS with SHA-384), type 0x0001 by its issuer. A token attests the device an attester
vouched for, not the rendering.
- **The billing rule.** An ad policy of version 2 (`WAP1`) names a viewability rule and can
require a claim for each impression, an audible run and an attestation. Each claim then gets
a verdict (reads, signed by the key its session is derived from, the impression's first,
the creative's own objects, the receipt's quartile, viewable, attested when required), and
the room verifier's spend-equivalence (live-ads §9.3 step 12) holds the **presented spend**,
the prices of the impressions whose claim verifies, to the decider grant's ceiling. A
stripped, redirected or forged beacon counts nothing. The text states plainly what a claim
cannot prove without attestation (a modified player, or a party making session keys of its
own, can state anything) and what attestation bounds and does not (a vouched device running
modified software still signs).
- **live-ads** takes the version-2 policy, the derived session and the spend that counts; a
policy of version 1 and every existing receipt verify as before, and `live-conformance/ads/`
is unchanged.
- **Reference and corpus.** `wai-rs` feature `live_ads` (`src/live_ads/presentation.rs` and
`presentation_corpus.rs`), which now takes `num-bigint` (already `oer`'s) for the RSA
arithmetic. `live-conformance/presentation/`, written from fixed seeds by
`wai_ad_presentation_vectors`, its issuer key RFC 9578 Appendix A.2's test key and its token
checked first: good impressions under each rule, attested ones on television outputs, and a
claim for each reason (too few pixels, a broken run, muted, a duplicate, a key that does not
name the session, an object not the creative's, a misstated quartile, no token, another
impression's token, an altered authenticator, a bad signature, a malformed claim, a session
with no decision, and an impression with no claim), with a room whose receipts pass the
ceiling only if every billed impression counted. Checked by `tests/ad_presentation.rs` and by
`live-conformance/ad_presentation_verify.py`, a second implementation from the text with its
own RSASSA-PSS, which requires every mutated copy to fail and catches each of its own checks
dropped.
- **A reference producer** in the browser client, `joule-moq-web/web/ads/presentation.js`: a
span recorder over a video element (an `IntersectionObserver`, the page's visibility and
focus, volume, playback position and frame counts) and the claim's writer and signer on
WebCrypto's Ed25519. Its tests turn observation sequences into the corpus's claims, byte for
byte. No page of the client plays ads yet.
### `wai.access.services`: caption quality anyone can recompute, audio description and sign language
- **The extension** ([access-services](extensions/access-services.md)) registers the record
`wai.access.services`. Captions are timed-text's, unchanged; its records are JSON texts in
the JCS form, each signed with Ed25519 under `wai:access` and named by its SHA-256.
- **Caption quality from the signed revisions.** A caption track's **latency** is each cue's
first display on the room clock less the capture of its first word: a sources record places
the words on a take, mapped to the room clock by the take's opening commitment (the take is
checked to derive from a `record_start` to the commitment's author), or on the room clock as
its signer states, and the class (`capture`, `stated`, `none`) says which. Its **stability**
is the revisions after first display and the words they changed; its **accuracy**, the word
errors of the first revision shown against a reference transcript or the reviewed final
revision, by a prefix alignment that does not count the words a partial has not reached.
Words fold ASCII case and edge punctuation, and the alignment walks back by one fixed rule,
so substitutions, deletions and insertions are the same everywhere. A quality record states
the figure and names its inputs by digest; a verifier recomputes it (`figure_mismatch`,
`figure_inputs_mismatch`). §9 maps each figure onto 47 CFR 79.1(j) and (k)(2), the two UK
regulator statements on live subtitling (2013, 2024) and EBU Tech 3370's authoring delay,
and says what is not computed (severity grading, speed, placement).
- **Audio description.** A description record holds the script (each event's interval, what
of the image it describes, its text), the ducking of the programme audio under each event,
and the voicing, on a separate track or by the sink with a pinned model, the script and the
voice each labelled human, machine or machine-reviewed. A voice must start within 35 ms
early and 45 ms late of the end of the duck and end no later than 45 ms after the duck
starts to rise, DAPT §5.5's bounds (`description_out_of_sync`). The record maps onto a DAPT
document (W3C Candidate Recommendation Draft of 2026-06-26), written by hand, with the
ducking as Mixing Instructions on each Script Event and the voice as an Audio Recording or
Synthesized Audio. A claim reports how much of each description is spoken over captioned
dialogue, a figure, not a refusal.
- **Sign language.** A signing record binds each object of an avatar pose stream, or of the
video of a signer, to the final caption revisions it translates, by digest
(`sign_revision_unknown`, `sign_revision_not_final`); a review signs off the translation.
Its figures are each segment's delay behind the speech and, for video, whether it meets the
resolution and frame rate EN 301 549 V4.1.1 clauses 6.5.2 and 6.5.3 state.
- **Completeness.** A claim declares a session's access tracks; a verifier checks each is
present, finished (every cue resolved, every description voiced, every cue signed), in its
language and within the tolerances the claim states, its reviews held and not rejected.
- **Reference and corpus.** The `access_services` module of `wai-rs` (feature
`access_services`) and `live-conformance/access/`, written from fixed seeds by
`wai_access_services_vectors`: a lesson with human and machine captions, a take, a
reference, quality records, a voiced description, a motion-captured signer and a claim; a
reviewed session with a sink-voiced description and a signer's video; a session with no
sources; and a negative control for every reason of §10, among them a misstated latency, a
broken revision chain, a signing bound to a superseded revision, descriptions spoken early
and late, and a claim over a track the verifier does not hold. Its second implementation,
`live-conformance/access_services_verify.py`, gives every case the same outcome and figures
and every DAPT document the same bytes, and every mutated copy of the corpus fails.
- **timed-text** §1 now points audio description, which it leaves out of scope, to this
extension.
### `wai.bearer.receipts`: one receipt on every bearer
- **The defect.** unidirectional-delivery §4 required a receipt for objects sent over a bearer
with no return path to "declare the bearer as unidirectional", and said a sink's later
reconciliation is a `claim` "under a class registered for it in jwp-receipts §2.4.4, which none
yet is". Neither had a mechanism: no field, class or vector. Both are now built, and §4 is
rewritten so its MUST is met and checked.
- **The extension** ([bearer-receipts](extensions/bearer-receipts.md)) registers the record
`wai.bearer.receipts`. The group and object receipts stay byte for byte what they were, on every
bearer; the corpus carries one group on all three bearer classes and checks that its bytes and
its object verdicts are the same. The bearer is declared beside the receipt, so one group
carried on several bearers has one receipt and a declaration for each, and no receipt already
issued changes.
- **Two claim classes in jwp-receipts.** `transmission` (§2.4.14): the publisher's declaration,
signed by the group's key and naming the group by its link hash, of the bearer's class
(`bidirectional`, `multicast`, `unidirectional`), the instance, and the transmission's energy
with its acquisition class or `null`; `attests` is `transmitted`, and anything else is refused
(`transmission_not_transmitted`). `reconciliation` (§2.4.15): a sink's later statement of the
objects it received, a bitmap and a root over their leaves under the group's own tree, checked
against the publisher's root without the objects (`reconciliation_root_mismatch`), refused from
the publisher's key (`reconciliation_by_publisher`) and over a leaf count or a group not the
group's; `attests` is `received`. Both are checked against the group at §3.1 step 6, and over
HTTP against the group of an index item (http-delivery §8.2). Their authorities `transmission`
and `reconciliation` are registered in §7.5; a transmission's energy figure is an item of its
own, as a session's is.
- **The verdict over a group** (bearer-receipts §6): `published`, `transmitted` or `reconciled`,
never delivered on the publisher's word. Declarations of one instance that differ are set aside
(`bearer_contradicted`); a reconciliation over an undeclared or contradicted instance is counted
nowhere (`bearer_not_declared`, `bearer_contradicted`); a key is counted once per instance, its
later reconciliations joined (`reconciliation_duplicate_sink`); a relying party may count only
keys it confirmed (`sink_not_confirmed`). A statement that a group was delivered to *k* sinks
holds only when *k* keys reconciled every object (`delivery_not_reconciled`,
`delivery_exceeds_reconciled`). A sink reports whether the instance it received over was
declared.
- **Energy per receiver** (§7): an upper bound, ⌈joules / max(1, distinct keys)⌉ at the
transmission figure's class, tightening as reconciliations arrive; never a measurement, never
0, none for an unmetered transmission, and stated as a count of keys.
- **No return path** (§8): a sink confirms under anchors provisioned in advance; a key log that
reaches it over the bearer is held, so a revocation sent over the bearer takes effect, and
anchors nothing (`anchor_on_bearer`), so a log forged on the bearer confirms nothing.
- **Bearer mappings** (§9), normative for the bytes WAI controls (envelopes whole, receipts and
claims as http-delivery's receipt files, key logs at their path) and informative for FLUTE
(RFC 6726), ETSI TS 103 769 V1.2.1, ETSI TS 103 770 V1.2.1, ATSC A/331:2026-04 with
A/333:2026-04 and A/360:2026-08, 3GPP TS 26.346 V19.3.0 and TS 26.517 V19.3.0,
`draft-ramadan-moq-multicast-00`, and ETSI TS 102 796 V1.8.1, each at the edition read.
- **Reference implementation and corpus.** `wai-rs` feature `bearer_receipts` (module
`bearer_receipts`; the bodies in `claim_receipt::bearer`); corpus
[`live-conformance/bearers/`](live-conformance/bearers/), regenerated by
`wai_bearer_vectors gen`; second implementation `live-conformance/bearer_verify.py`, written
from the text, which also mutates the corpus and drops each of its own checks in turn and
requires each to be caught; `claim-conformance/claim_verify.py bearer` reads the corpus's
claims through jwp-receipts §3.1 steps 1–4.
### `wai.media.direction`: a director's storyboard, keyframes and sequence, bound to the video and checked
- **The extension** ([direction](extensions/direction.md)) registers the record
`wai.media.direction`. A director, a person by an Ed25519 key, signs records at four levels of
control, and each record states the levels it sets: a **storyboard** (`wai-storyboard`): the
shots, each with its duration, its subjects by reference picture (one view per angle) and the
framing it opens on, and optionally their order; **keyframes** (`wai-keyframes`): at stated
frames of a shot, each subject's box on screen, actor relative, its size class, its screen
position and its angle, and a camera window over the shot's plate, with `hold`, `linear` or
`ease` interpolation between keys pinned in integers, and regions of frames held to a reference
picture; and a **sequence** (`wai-sequence`): which shots, which frames of each, in which order,
joined by cuts or dissolves.
- **The directed output** (`wai-directed`), signed by the tool that made the video, names the
records and its directors, the layout of every frame of every shot as made (plate, camera
window, each subject's view and box, the regions held), the timeline as made and the output's
frames by digest.
- **Exact where the render is.** Under `replicate` the frames are the pinned integer render of
the extension over layers that `wai.generate.spectral` regenerates or the verifier holds: the plate through
the camera window by the nearest sample, each subject's view scaled to its box, the held regions,
and an integer blend in a dissolve. Every frame recomputes, so the checks are exact: each cut on
its directed frame (`cut_mismatch`), each duration (`duration_mismatch`) and the order
(`order_mismatch`), each transition (`transition_mismatch`), each subject where its keyframes
and their interpolation put it and its size, position and angle classes under the pinned
framing measure (`framing_mismatch`), each held region (`region_mismatch`). Under `attest`, a
float generator's frames, the same checks run on the tool's stated layout and the outcome is
attested with the class of what stated it; a float generator's picture claimed exact is
refused (`check_mismatch`).
- **Governance and authorship evidence.** Every element governs the output frames that show what
it directed, and the directed output states them; a claim of frames an element does not govern
is refused (`governance_mismatch`). An accepted case gives a per-frame map of who directed each
frame at each level, beside what made its samples. Each element maps onto a `direct` step of the
contribution record, with its frames, and onto a C2PA 2.4 `c2pa.edited` action whose changes are
frame ranges (and the rects of a held region), one actions assertion per director; and, in an
informative section, onto OpenTimelineIO's serialized schemas. WAI decides no legal question.
- **The corpus** ([`live-conformance/direction/`](live-conformance/direction/)), written by
`wai_direction_vectors gen` from fixed seeds, holds a storyboard of three shots and two subjects,
a cinematographer's keyframes (a camera easing across a plate, a push in from medium to close)
with a held region of a drawn reference, and an editor's sequence that reorders, trims and
dissolves, at 64 by 36 pixels so every frame recomputes; the storyboard fixing its own order; an
order left to the tool; the same direction attested; and a control of one defect for each reason,
among them a cut a frame early, a box a pixel off its track, a size class the box does not
measure, a record signed by a non-party, a swapped order, a float plate claimed exact and a
governance entry that claims every frame. Its second implementation,
`live-conformance/direction_verify.py`, written from the text and sharing no code with `wai-rs`,
runs the generator in its own arithmetic after checking it against its conformance vector,
renders every frame and writes every statement itself, requires every mutated copy of the corpus
to fail, and drops each of its own checks in turn and requires the corpus to catch the weakened
verifier.
### `wai.media.influence`: who conditioned an output, and their share of what it earns
- **The extension** ([influence](extensions/influence.md)) registers the record
`wai.media.influence`. An owner, any key (a person, a collective, a label), publishes a work with
signed **terms** (`wai-terms`): its `wai.ai.usage` statement, the acts of generation it allows, a
per-use price or a revenue share, the tier a use of it must reach, and when the terms are in
force; a **catalogue** (`wai-catalogue`) is a signed set of works one set of terms governs. Terms
and catalogues are their owner's only by its signature (`not_signed_by_owner`). A work is a style
or reference image, a voice model, an adapter or another prior, a taste prior or a catalogue, named
by digest; an owner's prior is published unchanged, pinned as SPEC §3.1 pins any parameter set.
- **Conditioning.** A generation, candidates or an inpainting of a contribution session names every
work that conditioned it in a **conditioning** record (`wai-conditioning`) beside the step, bound
to it by its link hash: contribution reads every record with exactly its members, so a member
added to the step would make every contribution verifier refuse the session. Each reference
names its work, the owner's terms in force, its weight and its use, checked against the terms
(`terms_mismatch`, `not_in_catalogue`, `terms_not_in_force`, and ai-usage's `usage_unanswered`
and `usage_disallowed`). The pinned conditioned generator, `wai-influence:spectral-blend`,
blends `wai.generate.spectral`'s picture with every reference image (tiled) and adapter (a
second model file) by weight in integers, so a verifier regenerates the step from the works it
names: a work left out, added or reweighted fails (`conditioning_mismatch`), and a reference
named for a step of the exact generator, which reads none, is refused too.
- **Attribution.** Every sample of the output has the steps that made it (contribution §7.2's rules
with the step in place of its performer), and the declared rule `wai-influence:share/1` divides
the output, 10^6 units a sample, among the people who made samples, the owners of the works that
conditioned the model by their weights (equal shares, the model counted as one, where none is
declared), the model's owner, works no one owns, and `unknown` and `undisclosed`, which are
never credited (`unknown_attributed`). The tool's **attribution** (`wai-attribution`) states the
shares, which sum to the whole (`shares_not_whole`) and which a verifier recomputes
(`shares_mismatch`) whenever every step behind the output is disclosed; a training-time estimate
is stated beside them with its estimator's class and never added (`estimate_mixed`). Each use has
a tier, `recomputed` where the generation was regenerated with its works, and an owner's terms set
the lowest it accepts (`attribution_not_met`).
- **Payment.** A settlement statement's line kind 2, reserved until now, is the **attribution
line** ([settlement](extensions/settlement.md) §7.8): per owner, object class and basis, the
output's acknowledged deliveries and bytes and `per_use` × deliveries + ⌊revenue × share ×
units / (10^6 × whole)⌋, rounded down once per bucket, the remainder staying with the party; the
statement's `amount` and `classes` remain the delivery lines'. A verifier recomputes every line
from the bases it verified: a disallowed use paid, a tariff in another currency
(`currency_mismatch`), an owner left out, and an output's deliveries settled with no line for its
owners (`attribution_unsettled`) are refused. A statement verified by settlement alone refuses an
attribution line whose basis it is not given (`record_missing`), and the settlement corpus's
reserved-line vector now says so.
- **Transparency report** (`wai-influence-report`): per owner and statement, the outputs its works
had units in, its buckets, the deliveries, the revenue and the amounts, signed by the statement's
party. The owner recomputes every amount from its own terms; an auditor holding the statement
checks that the report lists exactly the owner's lines (`report_incomplete`, `report_mismatch`).
It names no other owner.
- **Reference and corpus.** `wai-rs` module `influence` (feature `influence`), the corpus
`live-conformance/influence/` written by `wai_influence_vectors gen`, and its second
implementation `live-conformance/influence_verify.py`, which verifies each session with
`contribution_verify.py`, regenerates every conditioned step itself and reads statements with
`settlement_verify.py`, now amended to read attribution lines.
### `wai.live.coperformance` in the live room: the clock signs, the relay refuses, the page performs
- **The room clock signs what only it knows** (`joule-moq-room`, `[co]`, co-performance §8). It
records each audience member joining when its events track appears, follows every member's
events and each publisher's media, and holds the room as a verifier does. It logs each object
it takes with its arrival and its due instant (the object's timestamp, which a page states on
the room clock) and serves the log (`GET /co/objects`), signs a mark for each object out of
turn under a schedule that marks or later than its publisher's budget, commits to each window
once it has closed and the inputs in flight have arrived and signs its selection under its
rule with its credits (a draw from a seed it drew when the window opened), and states each
participant's fidelity, every `fidelity_every_s` and when a lead asks. A page asks it for a
turn schedule, a handover or a window over HTTP, its grant presented with a proof of
possession over a challenge the service issued (live-rooms §10.5.3), and its powers decide:
the room's first publisher holds the direct power (`lead`), the turn's holder may hand its
turn over. A publisher may ask for a grant that marks it an AI participant.
- **What the clock signs is what the verifier recomputes.** `wai-rs`'s `co_performance::replay`
replays a room through §9.2 step 4, and a clock reads from it the commitment, the selection,
the fidelity statement and the marks the verifier will recompute (`Replay::commitment`,
`selection`, `fidelity`, `marks_for`) and the floor (`FloorBook`). `tests/co_performance.rs`
recomputes every commitment, selection and fidelity statement of the corpus's accepted rooms
from the room as it stood just before each, byte for byte, and every object's marks.
- **The relay refuses invalid audience inputs** (§7.3): without the audience-input power of the
grant its author's session proved, to a window it does not hold or one already committed, of
a type the window does not take, past its author's power or its policy's rate, that does not
read, or whose signature fails. Each is withheld from every object that carries it and counted
by reason in the session's summary (`co_refused`), as is each object held back out of turn
(`refused_out_of_turn`). A grant head (`joule-moq`) now reads a grant's attributes and its
policy's rate window.
- **The room page performs** (`joule-moq-web/web/room/co.js`). It shows the turn schedule and
who holds the floor, each participant's intents as upcoming lines and what became of them, the
window open now with an input box for a viewer whose grant allows it, each selection with its
credits, and the clock's fidelity statement; a lead's controls ask for turns, a handover, a
vote, prompts and a fidelity statement. A performer's page sends media only while it holds the
floor, a guard inside each turn, and states its media's timestamps on the room clock. With
`ai=scripted` the page is a demo AI participant: a drawn picture and a tone, and a scripted
line announced ahead of each it says. `wai-live-web` reads every statement, signs a page's
intents and inputs, and verifies the room as a page holds it (`Room.coVerify`;
`wai_live_web co-verify` on the host).
- **The extension's text** now says what a relay does with an input naming a committed window,
what it counts, how a room clock that is a session of its own on the relay takes arrivals and
signs marks, when it commits and states fidelity, how a room key states a statement a
participant asks for, and that a performer states an object's due instant as its timestamp
on the room clock.
### `wai.live.coperformance`: intents, turns and audience co-creation in rooms
- **The extension** ([co-performance](extensions/co-performance.md)) registers the record
`wai.live.coperformance`. The room authority signs a co-performance policy, `WPP1`, chained
by the instant it takes effect; every other statement is a `meta` payload of
[live-events](extensions/live-events.md), signed by its author in its chain, and the
descriptor's and the grant's layouts are unchanged.
- **Intents.** A participant announces, ahead of the instant it plays and for a horizon the
policy bounds, the material it means to play: an object by the digest of its payload, notes,
a text line, or a camera move (`WPI1`). A verifier finds what played (the object nearest the
planned instant on the announced track, or the author's chat or caption) and compares it
under a pinned rule: digests equal, notes with their pitches equal and their onsets and
durations within the policy's tolerance, text equal byte for byte, a camera's pan and tilt
within the angle tolerance and its zoom equal. Each intent is kept, diverged or missed, and
a room key's fidelity statement (`WPF1`) states per participant how many of each, which the
verifier recomputes as of the statement's instant (`fidelity_mismatch`).
- **Turns.** A turn schedule (`WPT1`) gives the floor in turns of microseconds or of bars at a
stated tempo, its holders taking turns in order, solo or as call-and-response pairs, and a
handover (`WPH1`) passes a turn from an instant. The schedule states its mode: under refuse
the relay does not forward an object published out of turn (`turn_violated`); under mark the
relay forwards it and the room clock signs a mark (`WPM1`, `turn_unmarked`). A grant's
`latency_budget_ms` bounds how late a participant's object may arrive, and a late object is
marked before any mix (`late_unflagged`); a mark the room's records do not support is
`mark_unfounded`.
- **Audience co-creation.** A grant's `audience_input` attribute is the audience-input power
and its limit per window; a member's input (`WPA1`) is a prompt, a sketch or a movement sample
by digest, or a vote, sent to a window (`WPW1`) that states its rule and its close. Once the
window closes, the clock commits to the eligible inputs (`WPK1`): the inputs sent before the
close and not restricted by a moderator's decision. A selection (`WPS1`) recomputes from the
commitment under a plurality vote, the top inputs by reactions, or a draw from a seed the
window committed to before any input; a pick sent after the close is `input_late`, and one
the rule does not give `selection_mismatch`. Every pick carries a credit: the member who sent
it, as an author of material, or the voters of an option together, as directors; each maps
onto a party and a step of a contribution record, a slot this revision names and writes
nothing into.
- **Reference and corpus.** `wai-rs` feature `co_performance` (`src/co_performance.rs`).
`live-conformance/coperformance/vectors.json`, written from fixed seeds by
`wai_co_performance_vectors`: a jam room in which a person and an AI participant trade
four-bar phrases, an audience room with a window of each rule, the audience room with a gap,
and a refused room for each reason of §10. Checked by `tests/co_performance.rs` and by
`live-conformance/co_performance_verify.py`, a second implementation that shares no code with
`wai-rs` and that drops each of its own checks in turn to show the corpus catches it.
### `wai.media.takedown`: takedown across every rendition and cache, against the regime's clock
- **The extension** ([takedown](extensions/takedown.md)) registers the record
`wai.media.takedown`. A service signs a **takedown** (`wai-takedown`) of a source (an object
by its content hash, a group, a track or a take) in answer to a signed **notice** or order
(`wai-takedown-notice`), or of its own initiative, under a **policy**
(`wai-takedown-policy`) that names its stores and the clocks it runs under.
- **Regime clocks are data.** A registry (`wai-takedown-clocks`) holds each clock by
identifier, type (`deadline`, `reply_window`, `unfixed`), duration and start, citing its
instrument and provision; a policy pins it by link hash. WAI publishes one at
[`registry/takedown-clocks.json`](registry/takedown-clocks.json): Public Law 119-12, section
3(a)(3); the Information Technology (Intermediary Guidelines and Digital Media Ethics Code)
Rules, 2021, rules 3(1)(d) and 3(2)(b), as amended by G.S.R. 120(E); Regulation (EU)
2024/1083, Article 18(4); and Regulation (EU) 2022/2065, Article 16(6).
- **The derivation closure**, computed exactly from deriving-edge records and synthesis origin
claims, transitively, with each group it reaches expanded from its delivery receipts
(`group_unexpanded` otherwise). A takedown's stated set that leaves a derivative out is
`closure_incomplete`; one that claims what no record derives is `closure_unsupported`; a
perceptual match (`wai-takedown-match`) listed as exact is `match_as_exact`. Copies made
outside every WAI derivation are out of reach, and the text says so.
- **Purges and the clock.** Each holder (a store of the policy, an edge that derived from a
covered object, a party that claimed to, a publisher of a covered group, and a party that a
`wai.delivery.settlement` acknowledgement, signed by its receiver, shows served it or, as a
hop, received it) signs a purge (`wai-takedown-purge`). The verifier computes the time to
removal from the clock's start to the last purge required: `purge_missing` past the deadline,
`deadline_missed`, and `window_not_observed` for a removal inside a reply window. A purge by a
party that never held what it names counts for nothing.
- **Counts** (`wai-takedown-counts`), per clock, ground, category and source, with the lower
median time to removal, recompute from the cases (`counts_mismatch`); a service gives each
takedown's statement-of-reasons record its link hash as `puid`.
- **Live moderation** is unchanged: its records are versioned, and a takedown beyond a room is a
sibling record.
- **The reference relay's DVR store** (`joule-moq-relay`) honours a signed takedown on the track
`[key, takedown]`: it forgets every covered object and track in every room, never stores an
identical copy again, withholds them from every FETCH, keeps its takedowns across a restart,
and signs a purge with the relay's key. It does not check the live fan-out.
- `wai-rs` module `takedown` (feature `takedown`); corpus
[`live-conformance/takedown/`](live-conformance/takedown/) (`wai_takedown_vectors`), and its
second implementation, `live-conformance/takedown_verify.py`.
### The browser sink: routes before decode, and the frame on screen bound to its receipt
- **The extension** ([browser-sink](extensions/browser-sink.md)) says how a sink in a web page
answers SPEC §4's "the sink advertises `cap`". For each object it builds a capability report
and routes the object to the user agent's decoder, a native host's or WAI's own WebAssembly
decoder. The user agent is asked only about codec strings the WebCodecs Codec Registry
(Registry Draft, 24 September 2026) lists: a capability with no such string (`wai.video.av2`,
`wai.video.vvc`, `wai.audio.iamf`) is never put to `isConfigSupported`, and an answer about
one is never read. The user agent's decoder is taken only where it is exact for the tier the
object needs: a need of `decode-equivalence` takes it only after a probe matches published
frame digests, and otherwise falls back to wasm. Each step's refusal has a reason code.
- **Constraint sets in the page.** [capability-constraints](extensions/capability-constraints.md)
gains its register (§3.1, the `wai:` parameters) and its evaluation, in browser-sink §5:
enumerations, minima and maxima ORed over sets, in integers.
- **Energy beside `powerEfficient`.** A measured figure per decode is reported beside Media
Capabilities' boolean, with its acquisition class ([energy-measurement](extensions/energy-measurement.md)
§2) and coverage; the boolean stays the user agent's word, and a decode with no measurement is
unmetered, never 0 J.
- **`contentBinding`.** Every frame a page decodes carries the content hash of the receipted
object it was decoded from ([jwp-receipts](extensions/jwp-receipts.md) §2.1), the link hash of
the group receipt that object verified under, and the tier its samples hold. The binding
survives `clone()`, serialization and transfer, gives nothing to the bytes `copyTo()` writes,
and does not survive re-encode. Until the WebCodecs VideoFrame Metadata Registry lists the
member, a page keeps it in a side table keyed by decoder and timestamp, and refuses a
timestamp given twice. The state of the frame on screen joins the binding to the receipts and
to [live-provenance](extensions/live-provenance.md)'s verdict. A draft registry entry and
issue text are in [`proposals/webcodecs-content-binding.md`](proposals/webcodecs-content-binding.md),
not filed. `contentBinding` is a wire identifier of the new kind `frame-metadata`, status
`proposed`.
- **Reference and conformance.** `wai-rs`'s `browser_sink` module (feature `browser_sink`) and
the corpus [`live-conformance/browser/`](live-conformance/browser/) with its second
implementation, `browser_sink_verify.py`. `wai-live-web` builds the same functions for the
page, and with its feature `int_video` a wasm decoder that verifies a `wai.video.int_motion`
object against its receipts and binds every frame. The room client routes each track before it
decodes it, binds every frame its decoders output, and shows each tile's state with
`?debug=binding`; its end-to-end scenario `binding` runs all of it in a browser.
### `moq-streaming-format`: the MOQT drafts the reference speaks
- **§6** says that nothing in the extension depends on the MOQT draft carrying the objects,
and that the reference implementation speaks `draft-ietf-moq-transport-22`, `-18` and
`-16`, chosen per session, and carries the extension's envelopes, `WAI_RECEIPT` properties
and receipts tracks unchanged on each. `joule-moq`'s README records the relays and drafts
they have been carried through, and the date.
### `wai.delivery.settlement`: delivery paid on what the party that received it signed
- **The extension** ([settlement](extensions/settlement.md)) registers the record
`wai.delivery.settlement`. The downstream party of a delivery, a next hop or a sink, signs an
**acknowledgement**, `WDA1`, of what it received in a window: per object class (`live`, `fetch`,
`fill`, `ancillary`), the objects by content hash under jwp-receipts §2.2's tree, the byte range
of each and their byte count, the item lists travelling beside the record under a digest it
signs. A party's acknowledgements to one upstream form a chain of windows that do not overlap.
A relay acknowledges upstream and is acknowledged downstream over the same content hashes, and
a hop's billable bytes are exactly those its downstream parties signed for. An acknowledgement
whose downstream is its upstream is refused (`self_acknowledged`).
- **Unacknowledged delivery.** A hop whose downstream will not or cannot sign states the delivery
in a **declaration**, the same record signed by itself, counted in a class of its own: never as
zero, never billed, and never for a byte the same party acknowledged
(`declared_and_acknowledged`). A unidirectional bearer settles under unidirectional-delivery §4.
- **Byte-range proofs**, `WBR1`: the covered chunks of a range and the chaining value of every
subtree of BLAKE3's chunk tree outside them, verified to the object's content hash; the head
proof of http-delivery §6 is the case of a range that starts at 0. A proof establishes the
object's length only when it reaches the last chunk, and its verifier says whether it did. An
auditor asks a downstream party for one over an item it acknowledged.
- **Paid retrieval.** A fetch below the live edge, a recording fetch, or a fill past a cache is
billed only to a room grant whose `WRP1` proof of possession the request carried, checked by
live-rooms §10.5.4 steps 2 to 5 and the seek power; one proof pays for one record
(`pop_replayed`); an ungranted retrieval is free (`paid_without_grant`).
- **The settlement statement**, `WSS1`, per party and period: lines by object class and payer,
the acknowledged bytes by class, the declared bytes, the party's joules by acquisition class
from its per-hop records (`WMH1`) and never one total across classes (`classes_summed`), and the
amount under a declared tariff, in millionths of a minor unit per 10^9 bytes, rounded down once
per line. A verifier recomputes every figure from the acknowledgements: a line billing bytes no
acknowledgement covers is `bytes_not_acknowledged`, any other difference `statement_mismatch`.
A line kind is reserved for a payee that is not a hop.
- **Logs.** Each acknowledged item of an HTTP delivery maps onto a `cdni_http_request_v1` record
(RFC 7937 §3.4.1), the acknowledgement's digest in a `remark` directive; a log carries no
counter-signature, so a converter maps a log onto declarations, never onto acknowledgements.
- **Viewers as relays over ciphertext.** An acknowledgement binds a sealed object's content hash,
over its sealed bytes, so a forwarder that cannot open what it forwards earns acknowledged bytes,
and proves ranges of them, on the terms of any relay.
- **Reference and corpus.** `wai-rs` module `settlement` (feature `settlement`), its generator
`wai_settlement_vectors` and `tests/settlement.rs`; the corpus
[`live-conformance/settlement/`](live-conformance/settlement/), 48 statements and 24 range
proofs; and its second implementation, `live-conformance/settlement_verify.py`, written from the
text, which requires every mutated copy of the corpus to fail and every one of its own checks
to be load-bearing.
### `wai.media.contribution`: who contributed what, computed from the process
- **The extension** ([contribution](extensions/contribution.md)) registers the record
`wai.media.contribution`. A tool signs a **session** naming its parties: people by Ed25519 key
(`human`, `audience`), each countersigning its own membership (`wai-membership`), and models
by digest. A person cannot be enrolled as a maker or a director of any sample without that
signature (`party_not_countersigned`).
- **Steps.** Every act is a signed step (`wai-step`) in a hash chain, signed by the person who
performed it or by the tool for a model: `author`, `generate`, `candidates`, `select`, `edit`,
`inpaint`, `transform`, `compose` and `reject`; `direct` is reserved for direction tracks.
Transforms and compositions evaluate pinned operations by name: deriving-edge's crop,
rotation, mirror, overlay and grid composition, synthesis's upscale and denoise, and one new
per-sample colour table, `wai-contribution:lut`. The exact generator is `wai.generate.spectral`.
- **The authorship map**, computed by re-executing each act's dependency rule: for every pixel
of the output, `made_by` (whose act produced its value) and `directed_by` (whose prompt, mask,
selection, parameters or arrangement conditioned it). A mixing filter takes the union of what
it mixes; a selection adds the selector everywhere; a composition takes each layer where it is
visible. Encoded with a palette and run-length rows, in one encoding, and bound to the output's
samples by its digest. Shares are exact sample counts per party for each of the two sets,
never one blended figure; an estimator's share rides beside them with its class.
- **Unknown is never zero.** An attested step's unpinned sample mapping leaves its samples
`unknown`, and a verifier marks samples from steps it does not hold `undisclosed`; neither is
ever credited to a party, and a stated map that credits an unknown sample is refused
(`unknown_attributed`).
- **What a verifier checks**: the chain and every step's inclusion in the record's Merkle root;
that an edit or an inpainting changed nothing outside its region, byte for byte
(`outside_region_changed`); that a selection is a member of its committed set
(`selection_not_in_set`) and that every candidate regenerates from its seed
(`regeneration_mismatch`); that each transform and composition recomputes
(`recompute_mismatch`); that the map recomputes (`map_mismatch`); and that the output's origin
claim is synthesized wherever a model made a sample (`origin_understated`).
- **Selective disclosure.** Steps are disclosed with inclusion proofs and prompts are salted
commitments; the outcome's tier says what was reached: `recomputed`, `checked` or `stated`.
- **Mappings.** One C2PA 2.4 actions statement per session, computed where every step is
disclosed (`c2pa.created` from an empty canvas, then one action per step behind the output,
with its software agent, its IPTC digital source type and its changed region); identity
assertions and live tracks, informative.
- **WAI decides no legal question.** The record states facts a reader applies its own rule to.
- **Conformance.** `live-conformance/contribution/`, written from fixed seeds by
`wai_contribution_vectors gen`, every reason of §11 refused at least once; `tests/contribution.rs`; and the second
implementation, `live-conformance/contribution_verify.py`, which runs the generator in its own
arithmetic after checking it against the generator's conformance vector, catches every
mutated copy of the corpus, and is caught by the corpus with each of its checks dropped in
turn. Audio maps by sample interval and video maps by frame are not part of this revision.
### `wai.ai.usage`: AI usage preferences bound to the bytes, and the agent's consumption claim
- **The extension** ([ai-usage](extensions/ai-usage.md)) registers the record `wai.ai.usage`.
A **usage statement** carries three published vocabularies verbatim, each in its own member:
`aipref`, a statement of preference of `draft-ietf-aipref-vocab-08` in its Structured Field
form (`train-ai`, `ai-use`, `search`); `tdmrep`, the TDM Reservation Protocol's
`tdm-reservation` and `tdm-policy` (Final Community Group Report of 10 May 2024); and `cawg`,
the entries of a `cawg.training-mining` assertion (Training and Data Mining Assertion 1.1).
WAI adds no category and maps no term of one onto another. Each member names the revision it
is read at, and a revision this one does not read is refused.
- **RFC 9651's Dictionary**, parsed in full: every bare item type, parameters and inner lists,
with a repeated key keeping its last value. A value that does not parse, which the vocabulary
would read as unknown throughout, is refused rather than carried.
- **Bound to the bytes.** The manifest member `ai_usage` puts the statement inside the envelope,
so the object's receipt signs it with the content: an object whose statement is removed or
altered no longer hashes to its receipt (`usage_unbound`). A room states its preference in a
room usage record, `WAU1`, signed by the room authority and chained by the instant it takes
effect, so a change never reaches what was published before it; the descriptor's layout is
unchanged. A grant's attribute `ai_usage` carries the terms one principal is granted.
- **HTTP and MoQ.** An origin writes `draft-ietf-aipref-attach-05`'s `Content-Usage` field and
the TDMRep fields from the signed statement (http-delivery §5.6); a client compares them by
meaning, so a respelling is not a conflict and a contradiction is (`header_conflicts`). A MoQ
catalog states a track's statement as `wai_ai_usage` (moq-streaming-format §4), checked the
same way (`catalog_conflicts`).
- **One precedence rule.** A grant's terms decide the uses they name; otherwise every signed
statement that governs the object, the most restrictive governing, as the vocabulary combines
statements; unsigned statements count only where nothing is signed. CAWG's `constrained`
reads as not allowed unless a grant says otherwise.
- **The consumption claim**: the jwp-receipts claim class `consumption` (§2.4.13 there), signed
by the agent under its `did:key`. It names the objects by content hash, the term it acted
under in every vocabulary their statements carry (or `null`, a use outside it), the statements
it saw and the grant it held. A verifier checks it against the objects, their statements and
the room, and refuses a use they disallow (`usage_disallowed`); the refusal is of the use, and
the claim stays its signer's statement.
- **The gate.** A relay or gateway MAY require claims before it serves an agent: claimed before
the fetch over HTTP, where an object's content hash is known first (the proposed request field
`WAI-Usage-Claim`), or within a window of groups over MOQT; and MAY require every use to be
allowed.
- **Reference and corpus.** `wai-rs` feature `ai_usage` (`src/ai_usage.rs`, with RFC 9651's
parser in `src/ai_usage/sf.rs`), and the class's body and rules in
`src/claim_receipt/consumption.rs`. `live-conformance/usage/`, written from fixed seeds by
`wai_ai_usage_vectors`: statements, receipted envelopes (a `WAI2` among them) with a stripped
and an altered statement, a room with chained records and agent grants, responses, catalogs,
claims accepted and refused at each rule, and gates. Checked by `tests/ai_usage.rs` and by
`live-conformance/ai_usage_verify.py`, a second implementation that shares no code with
`wai-rs`, parses Structured Fields, canonical forms, receipts and claims itself, requires every
mutated copy of the corpus to fail and every weakened copy of itself to be caught;
`claim-conformance/claim_verify.py` reads the class's steps 1 to 4. The registry lists the
record, the magic `WAU1`, the catalog field `wai_ai_usage` and the proposed field
`WAI-Usage-Claim`.
### `wai.media.synthesis`: what any piece of media is, in two marking layers, with reviews and labels
- **The extension** ([synthesis](extensions/synthesis.md)) registers the record
`wai.media.synthesis`. An **origin claim** (`wai-origin`) says of one object, group or track
that it is captured, reconstructed (a codec, neural or not, rebuilding what was captured),
assistive (an edit that does not substantially alter its input) or synthesized (generated).
It names its step (`capture`, `convert`, `assist`, `generate`), the models the step ran, the
oversight of a generation, and each input by its content hash and the link hash of its own
claim. Its class is the greatest of its step's and its inputs', so it rises along a chain of
derivations and never falls; a transcode of a generated video is synthesized. A codec is a
conversion: it names no model and keeps its input's class and source, as energy-binding §6.1
has a decode.
- **IPTC and C2PA as functions of the claim.** The digital source type (`digitalCapture`,
`algorithmicallyEnhanced`, `trainedAlgorithmicMedia`, `compositeWithTrainedAlgorithmicMedia`,
`compositeSynthetic` and the rest) follows from the step, the inputs, the models and the
oversight; the C2PA statement is the actions (`c2pa.created` with the source, or
`c2pa.opened` then `c2pa.converted` or `c2pa.edited`) and one `c2pa.ai-disclosure` per trained
model, written in JCS. The mapping keeps timed-text's for captions.
- **Assistive claims checked, not trusted.** Two integer derivations are pinned: a 2× upscale
with `wai.video.int_motion`'s half-sample rounding, and a 3 × 3 binomial denoise. Evidence
names the input's and the output's sample digests; a verifier holding the input's decoded
samples recomputes the output and refuses a mismatch. A float or unpinned step states
`attest` and is reported as asserted; a policy can require proof.
- **Two marking layers.** The signed claim is the first; the second is a watermark whose 8-byte
payload is the claim's class and the head of its link hash. A marking record names the scheme
and the payload embedded, a detection record what a detector read, and a verifier refuses a
detection that disagrees with the claim or finds no mark in a marked object, and, under a
policy that asks for both layers, a synthesized object with no marking. No watermark scheme is
registered: the scheme is a slot a later revision fills.
- **AI participants in rooms.** live-rooms §4.4's grant attribute `ai` names the model that acts
for a participant; its chat, reactions and captions state origin 2 or 3 with that model, and
the room verifier refuses one that does not (`ai_unmarked`, live-rooms §9.2 step 6). Chat and
reaction events of live-events gain an optional `origin` and `model` after their last field.
Every track an AI participant publishes needs a synthesized track claim naming its model.
- **Certified synthesis.** A review (`wai-review`) is a signed verdict over one exact
reconstruction at `decode-equivalence` or `generation-equivalence`. A player presents it only
where its latest presentation of the object holds that tier on its decode path and output the
reconstruction reviewed, so one review holds for every viewer of exact output.
- **Labels.** A label policy (`wai-label-policy`) says which classes are labelled, with which
visual and audible assets, at what interval and grace; a player's signed log (`wai-player`) of
presentations, labels shown, resumptions after a break and reviews presented is held to it:
a label at first exposure, after each interruption and within the interval. The EU icons the
Code of Practice's Annex 1 publishes are pinned by SHA-256; no audible EU label is published.
- **Reference and corpus.** `wai-rs` feature `synthesis` (`src/synthesis.rs`).
`live-conformance/synthesis/`, written from fixed seeds by `wai_synthesis_vectors`: a
capture's chain through a neural codec and both pinned derivations, a float super-resolution
asserted, generated media in both layers, a room with an AI host, a certified and labelled
session, and one refused case per reason. Checked by `tests/synthesis.rs` and by
`live-conformance/synthesis_verify.py`, a second implementation that shares no code with
`wai-rs`, verifies each room with `live_rooms_verify.py`, runs the derivations and writes each
statement itself, and requires every mutated copy of the corpus to fail. The registry lists
the record and its document kinds.
### `wai.edge.derivation`: an edge that derives, under its own receipts, chained to its source
- **The extension** ([deriving-edge](extensions/deriving-edge.md)) registers the record
`wai.edge.derivation`. An edge evaluates an operation of derived-tracks (`wai.derive.*`) over
8-bit RGB samples, rebuilds an object at a lower rung for a sink that lacks its capability (a
frame of a `wai.video.int_motion` clip as a `wai.image.png` still or a `wai.image.jpeg`
picture), or explodes a `WAI2` component into its `WAI1` envelope. It alters nothing it
received: each output is a new object, published in a group of the edge's own track under its
key, and its manifest is pinned, with `derived_from` naming a single input.
- **The record** names each input by content hash, the group receipt that carries it, the frame
it was read at, the digest of its samples and the record that derived it where another edge
did; the derivation, its parameters and the encoder where one is not pinned; the output by
content hash, receipt, size and samples digest; and the energy with its acquisition class,
which must agree with the output's object receipt. It is JSON under jwp-receipts §2.4.3's
encodings, signed over its canonical form and identified by a BLAKE3 hash.
- **The check.** `replicate` when every step is pinned: the inputs' reading (packaging §5's
still reader, which here refuses a chunk that could state a colour representation; the
`WIV1` keyframe-0 decode), an operation exact under derived-tracks §2 (every registered one
but scaling, by the registry's own composition rule), and the still writer of packaging §5
or SPEC §7.1's explode. A verifier holding the inputs re-executes the hop and compares the
output byte for byte. `attest` otherwise: the inputs and every pinned step before the
attested one are still checked.
- **Verification** follows a derived object hop by hop to the publishers a sink subscribed to:
each record read and its signature checked, its output bound to the edge's receipts, its
inputs to theirs, each derived input to the record that derived it, each replicate hop
re-executed where its inputs are held, and each edge's authority, `edge/derive`, from the
origins of the sources its chain reaches (registered in jwp-receipts §7.5). A sink's policy
may refuse attested hops, require re-execution, or require authority. The facts state each
hop's check, how it was checked, its energy and authority, the sources reached, and the
energy per acquisition class, never summed across classes.
- **Provenance carried forward.** A record lists, by digest, every provenance item of every
input: the claims about it, the C2PA Manifest Stores bound to it or embedded in it (a PNG's
`caBX` chunk, C2PA 2.4 §A.3.2) and every item the records below it carry, each in a system or
a personal compartment, read in the split California Business and Professions Code
§22757.1 makes (AB 853, Statutes of 2025, Chapter 674, whose §22757.3.1 forbids large online
platforms to knowingly strip system provenance from 1 January 2027). A hop that leaves one out
is refused (`provenance_stripped`), and the sink holds every item's digest from the subject's
record alone. This revision carries personal items as it carries system ones; redaction under
a stated rule is to come.
- **An AI-origin label** (`assist`, `generate`) on every input and output, which
may rise along a chain and never fall (`synthesis_dropped`); a step that synthesizes content
labels its output.
- **Refusals** from `record_malformed` to `attested_refused`, among them a derivation not
registered, inputs that do not verify, a hop passing an edge's output off as a source object
(`chain_skips_hop`), an output that does not match its recomputation (`replicate_mismatch`),
and an edge the source's origin does not list (`edge_not_authorised`).
- **Reference and corpus.** `wai-rs` feature `derive_edge` (`src/derive_edge.rs`), and the
reference edge `wai_edge`, which derives from a bundle of objects and receipts after verifying
each input, and verifies a derived object back to its source. The still writer and reader of
packaging §5 move from `wai-pack` into `wai-rs` (`png_still`), which `wai-pack` re-exports.
`edge-conformance/`, written from fixed seeds by `wai_edge_vectors`: replicate hops of every
operation, rebuilt stills, an explode and chains of several hops, attested hops, policies, and
controls for every reason. Checked by `tests/derive_edge.rs`, which also runs the reference
edge twice and verifies the chain it makes, and by `edge-conformance/edge_verify.py`, a second
implementation that shares no code with `wai-rs`, re-executes every replicate hop, requires
every mutated copy of the corpus to fail and the corpus to catch it with each of its own
checks dropped. The registry lists the record and the document kind `wai-derivation`.
### `wai.live.provenance`: C2PA 2.4 live video provenance for room tracks and takes, tied to the JWP receipts
- **The extension** ([live-provenance](extensions/live-provenance.md)) registers the record
`wai.live.provenance`: C2PA 2.4 §19's live video provenance, written exactly as §19 defines
it, for a room's CMAF tracks and a device's CMAF take. A room track packaged as CMAF travels
under `draft-ietf-moq-cmsf-01`, each MoQ group one segment and each object one CMAF chunk.
A segment is its C2PA boxes followed by its group's objects and nothing else, so the
segment's `c2pa.hash.bmff.v3` and the group's JWP receipt commit to the same chunks; the
segment's sequence number is its first `mfhd`'s, following from the receipts' leaf counts.
- **Both methods.** Per-segment C2PA Manifest Boxes: each manifest holds the hard binding, the
actions, `c2pa.livevideo.segment` (the stream identifier and the manifest before it) and a
group assertion naming its receipt by link hash and root. Verifiable Segment Info: an `emsg`
of version 0 per segment carrying a COSE_Sign1 by a session key over the segment-info map;
the session keys, OKP Ed25519 keys bound to the claim signer's certificate, are delivered in
the initialization manifest and rotated by a manifest in a segment, and the session key also
signs the group's receipts. A take uses both at once, its device's key the session key, and
its stream identifier its take id's, as capture-binding §11 had them.
- **The profile.** One manifest per store; EdDSA throughout, deterministic as C2PA §13.2.1
recommends for streaming; an X.509 chain of version 3 Ed25519 certificates with
`c2pa-kp-claimSigning`; four whole-box exclusions in every hash; every manifest identifier
derived from the stream, the manifest's role and the segment's sequence number. Two readings
of C2PA 2.4 are stated: the VSI box's exclusion offset is 12, where `scheme_id_uri` begins,
since the 0 §19.4.1 prints compares a box's size; and a `signerBinding` is detached with a
`nil` payload, as §13.2.3 requires, where §18.25's example prints `h''`.
- **AI disclosure.** Every manifest carries the IPTC digital source type and one
`c2pa.ai-disclosure` per model that the stream's label gives, as timed-text §10.3 gives them
for captions; a compositor's output is `compositeCapture`, `composite` or
`compositeSynthetic` from its inputs' labels and carries their models' disclosures. A stream
labelled machine-made without the marking is refused. This is the machine-readable marking
Regulation (EU) 2024/1689 Article 50(2) describes, stated as an engineering requirement.
- **Dynamic and redundant packaging.** C2PA 2.4 treats content packaged on request as live
(§19.2.1) and recommends deterministic signatures for it, naming ISO/IEC 23009-9 (§13.2.1).
With derived identifiers, deterministic CBOR and Ed25519, every byte of a segment is a
function of its group's objects, the stream and the claim signer's key: packagers that share
a key write the same segment, and packagers that do not chain across a failover, their
segments' objects under the publisher's one receipt. Checking two encoders' outputs by
re-encoding under a `replicate` capability is stated as not in this revision.
- **Repository receipts** (C2PA 2.4 §18.27). A registered initialization segment keeps its
manifest box as `original` and ends with an `update` box: an update manifest whose one
ingredient is the parent, with the parent's validation results, and whose
`c2pa.repository-receipt` carries, as the repository's proof, an RFC 9942 receipt of
inclusion of the parent's manifest hash in the repository's RFC 9162 log, verified against
the repository's key.
- **Carriage.** Over HTTP, the segment files. Over MoQ, each segment's C2PA boxes ride a
provenance track, `<layer>_c2pa` (`packaging` `wai-c2pa`, named in `wai_c2pa_track`), whose
group *g* is segment *g*'s C2PA boxes, so the media objects stay the chunks a CMSF player plays
and the receipts commit to; the initialization segment rides the catalog's inline data.
- **Thirty-three reasons**, from `segment_malformed` to `segment_missing`, each with the C2PA
2.4 §15 and §19.7 codes a C2PA validator reports for it, where C2PA checks it.
- **Reference and corpus.** `wai-rs` feature `live_provenance` (`src/live_provenance.rs` and
its `bmff`, `jumbf`, `cose`, `x509` and `corpus` modules), with no C2PA library and no new
dependency. `live-conformance/c2pa-live/`, written from fixed seeds by
`wai_live_provenance_vectors` over the packaging corpus's AV1 floor: a camera track under each
method (the session key rotated at its third segment), a track labelled machine-made, a
compositor's output, a take, a failover between two packagers and a registered stream; and
one negative per reason, among them a segment whose bytes
changed, a missing segment and a reordered one, a manifest bound to the wrong receipt, a
stream identifier that differs, an expired and a time-stamping end entity, and a machine-made
track without its disclosure. Checked by `tests/live_provenance.rs` and by
`live-conformance/c2pa_live_verify.py`, a second implementation that shares no code with
`wai-rs` and requires every mutated copy of a good vector to be refused. The corpus's
initialization segments, the registered one with its update manifest included, also validate
in the C2PA command-line tool; the tool opens no media segment (`c2pa-live/README.md`). The
registry lists the record and the catalog values `wai-c2pa` and `wai_c2pa_track`.
- **capture-binding §11** now points here for the C2PA side of a take, and is implemented.
### `wai-ops/1-i32` on GPUs, and the tier where a run showed it
- **`wai/wai-ops-gpu`**, an operator implementation of `wai-ops/1-i32` (integer-operators §9.1) in WGSL compute kernels through wgpu: Metal, Vulkan and DX12 natively, and a browser's WebGPU. WGSL has no 64-bit integer, so the kernels use 32-bit words only:
- a graph whose certificate meets `acc32` runs with one word per value, its two `product` intermediates formed from 16-bit halves (§7.3);
- every other graph runs with each `acc` value in two words and each `wide` and `product` value in four, exact 64-bit and 128-bit arithmetic built from 32-bit words, an `acc64` implementation (§7.3);
- a graph with `layernorm_c`, `l2norm`, `softmax`, `sigmoid` or `elu` runs on the CPU, and `support` names the operator that sent it there.
The host makes the interpreter's run-time checks, in its order, before the device runs anything; a `check` tensor that leaves its range sets its operator's flag, and the first flagged operator refuses the decode with `field_range`. Each device passes an arithmetic probe against 128-bit host arithmetic before it runs a graph.
- **wai-rs `ops_gpu`** (off by default): `int_ops::run_graph` is now where every integer decoder runs a graph, and inside `int_ops::gpu::with_gpu` it runs each graph on the device, counting what ran where and why.
- **Tests that do not pass on a skip.** `wai-ops-gpu/tests/`: the probe on a million cases; every graph vector of `ops-conformance/` that the subset covers, at both widths, against the committed results and the interpreter; eighteen kernel edits, each of which the corpus must catch; and the 32-bit kernels shown to differ on graphs their certificate keeps from them. `wai-rs/tests/ops_gpu.rs`: every committed vector and refusal fixture of every capability that runs a graph, with its graphs on the device, against the CPU decode, graph by graph. `wai-ops-gpu/browser/` runs the corpus in a page's WebGPU. With no adapter each prints SKIPPED, and fails under `WAI_OPS_GPU_REQUIRE`.
- **determinism-tiers §5** now meets `decode-equivalence` for a decoder's `wai-ops/1-i32` graphs on the GPUs and backends a run showed equal, and lists them; §6 records the runs. Nothing is claimed for another adapter, and nothing for an NPU.
- **`tools/ops-gpu-bench`** times each capability's decode with its graphs on the CPU and on each adapter, and reads the energy counters the host has.
- **CI:** `wai-ops-gpu.yml` runs the Metal and browser legs on the `macos` runner, and requires on a runner with no GPU that the device tests skip without `WAI_OPS_GPU_REQUIRE` and fail with it.
### wai-quantum 0.7.0 and wai-quantum-cli 0.2.0: one OpenQASM 3 reader, synthesis, verdicts and repair, estimates, device targets, the debugger, error correction, sets and a durable service, views and a course, and a version contract
This release carries the sixteen entries below it, down to the study of the co-design, to the package registry. `wai-quantum-cli` 0.2.0 requires `wai-quantum` 0.7.0 and `wai-quantum-qpu` 0.3.0, which requires 0.7.0 too; the job service is 0.2.0. The Python package stays 0.1.0 and the editor extension keeps its version. What each version now promises is the policy, `wai-quantum/VERSIONING.md`; nothing is deprecated.
- **wai-quantum 0.7.0:**
- one OpenQASM 3 reader for every surface (`qasm::read`), every refusal and warning under a stable code (`qasm::diag`, the `Q` codes), and the job layer's refusals under theirs (`engine::refusal`, the `J` codes);
- synthesis and certified compilation (`engine::synth`, `engine::compile`): every pass checked where it acts and one certificate per compile, to the basis `clifford_t` within a stated `ε` or `standard`; a job may ask to be compiled first;
- a verdict for every answer about a program (`engine::verdict`): the level the engine checked to, from refused to proof, intents checked by proof or by evidence, and repairs held to the flagged lines (`engine::repair`);
- resource estimation from programs (`engine::estimate`), each estimate sealed in the resource-estimate receipt family;
- device targets (`engine::targets`): a device's calibration snapshot read into one document, `wai.quantum.target/1`, its noise derived, a program placed where it is most likely to succeed, and the target's hash in the receipt;
- a debugger with branch enumeration and exact weights, and statistical tests (`engine::debug`, `engine::stats`);
- the editors' language service and formatter (`engine::editor`), the agent front door's guide (`engine::guide`), a job on the person's own provider (`engine::provider`), and the study (`engine::study`);
- sets and sweeps, one receipt over their Merkle root (`engine::sets`), and a result's provenance (`engine::run::run_with_provenance`); the processor packages' energy on Linux (`engine::energy`);
- error correction for every surface (`engine::qec`): colour codes, distance certificates by exhaustive search, correlated matching, sliding-window decoding and thresholds; mitigation in a job (`engine::mitigate`: zero-noise extrapolation, error amplification and cancellation from a learned noise model, Clifford data regression, virtual distillation, readout inversion); Pauli twirling and dynamical decoupling as checked compile passes;
- the engine's answers as views inside agent hosts (`engine::views`), the answer view layer as a draft extension (`engine::answer`), and correctness records kept only when asked (`engine::telemetry`);
- the circuits course, each exercise checked by the engine (`engine::course`), in English and Simplified Chinese;
- versioned JSON Schemas for every document it writes (`engine::schema`), each result naming the engine that made it and its schema inside what its receipt seals.
- **wai-quantum-cli 0.2.0:** `new`, `test`, `setup-agent`, `mcp config | install`, `docs`, `lsp`, `fmt`, `dap`, `diagnostics`, `compile`, `repair`, `estimate`, `debug`, `targets`, `providers`, `qec`, `task`, `eval`, `learn`, `run --intent | --compile | --repair | --target | --provider | --program | --sweep | --mitigate`, `submit --dry-run | --detach`, `compile --twirl --dd`, `--lang` and `version`; the MCP tools `repair_qasm`, `compile_qasm`, `qasm_diagnostics`, `estimate_resources`, `debug_step`, `debug_branches`, `list_targets`, `show_target`, `run_on_target`, `providers_list`, `provider_estimate`, `run_on_provider`, `qec_code`, `qec_decode` and `qec_threshold`, and views for a host that draws them, every tool with its output schema and every answer about a program with its verdict.
- **The job service 0.2.0:** a durable store (by default `~/.local/share/wai-quantum-service/store`; `WAI_QSVC_STORE` names another, or `memory`), sets and sweeps, dry runs, status events, rate-limit fields, rotating receipt keys, the error-correction endpoints, views and its own view tool, an OCI image and a scheduler's interface; the MCP tools `run_quantum_set`, `dry_run_quantum_job` and the `qec_*` three.
- **wai-quantum-qpu 0.3.0:** the open provider protocol (`wai.quantum.provider/1`), its reference provider and conformance checker, and a registry whose credentials stay in the keychain.
- **What changed that a caller may have to change** (`cargo-semver-checks` against 0.6.16 and 0.2.0): `engine::catalog::Request` gained `diagnostics`, `compiled`, `compile_passes`, `repaired` and `mitigate`, `engine::catalog::Plan` gained `target`, and `qasm::Program` gained `extra`: take them from `catalog::read`, `catalog::plan` and `qasm::read` rather than writing them out. In `quantum_frame`, `Circuit` gained `detector_coords`, `tags` and `op_tags`, `ErrorModel` gained `detector_coords`, `Mechanism` gained `tag`, and `Op` gained the variant `Observe`; `quantum_resource_receipt::Program` gained the variant `Counted`: a `match` on either needs that arm, and a struct written out field by field needs the new fields. In `wai-quantum-qpu`, `JobInfo` and `JobRecord` gained `calibration` and `charged`. Every result now names its engine and schema (a set's head, an error-correction answer and a course check among them), so a stored result's bytes and its receipt's digest differ from 0.6.16's for the same request; a receipt 0.6.16 issued still verifies. The job service keeps its jobs on disk unless `WAI_QSVC_STORE=memory`, and a job it seals carries the result's `provenance`.
- **Breaking:** `api:rust:wai_quantum::engine::catalog::Request::compile_passes`: a job may ask for compile passes (twirling, dynamical decoupling) before it runs. A struct literal adds `compile_passes: None`, which runs as before, or builds the request with `catalog::read`.
- **Breaking:** `api:rust:wai_quantum::engine::catalog::Request::mitigate`: a job may ask for error mitigation. A struct literal adds `mitigate: None`, which runs as before, or builds the request with `catalog::read`.
- **Breaking:** `api:rust:wai_quantum::engine::compile::Options::dd`: the compiler gains a dynamical-decoupling pass. A struct literal adds `dd: None`, which compiles as before.
- **Breaking:** `api:rust:wai_quantum::engine::compile::Options::twirl`: the compiler gains a Pauli-twirling pass. A struct literal adds `twirl: None`, which compiles as before.
- **Breaking:** `api:rust:wai_quantum::engine::refusal::Outcome::Defaulted`: the answer view layer's outcomes join the job layer's in one table of codes. A `match` over `Outcome` adds an arm for it, or a `_` arm.
- **Breaking:** `api:rust:wai_quantum::engine::refusal::Outcome::Dropped`: as `Outcome::Defaulted`: a `match` over `Outcome` adds an arm for it, or a `_` arm.
- **Breaking:** `api:rust:wai_quantum::engine::refusal::Outcome::Warned`: as `Outcome::Defaulted`: a `match` over `Outcome` adds an arm for it, or a `_` arm.
- **Breaking:** `api:rust:wai_quantum::engine::schema::FILES`: the committed schemas grew from 11 to 22 with the documents this release adds. Code that names the array's length iterates over `FILES` instead.
- **Breaking:** `api:rust:wai_quantum::quantum_frame::Circuit::detector_coords`: the error-correction circuit text keeps detector coordinates when it is read and written back. A struct literal adds `detector_coords: Vec::new()`, which reads as before.
- **Breaking:** `api:rust:wai_quantum::quantum_frame::Circuit::op_tags`: the circuit text keeps each operation's tag. A struct literal adds `op_tags: Vec::new()`, which reads as before.
- **Breaking:** `api:rust:wai_quantum::quantum_frame::Circuit::tags`: the circuit text keeps its tags. A struct literal adds `tags: Vec::new()`, which reads as before.
- **Breaking:** `api:rust:wai_quantum::quantum_frame::ErrorModel::detector_coords`: the error-model text keeps detector coordinates. A struct literal adds `detector_coords: Vec::new()`, which reads as before.
- **Breaking:** `api:rust:wai_quantum::quantum_frame::Mechanism::tag`: the error-model text keeps each mechanism's tag. A struct literal adds `tag: None`, which reads as before.
- **Breaking:** `api:rust:wai_quantum::quantum_frame::Op::Observe`: a Pauli-target observable is now an operation of its own. A `match` over `Op` adds an arm for it, or a `_` arm.
- New refusal codes: `J007` a set that does not read, `J008` a mitigation, `J009` an error-correction request, `J033` an API key a private deployment requires, and `J100` to `J112` the answer view layer's.
- Every lockfile that holds one of these crates moves with them (seventeen, the views' engine among them), so a `--locked` build still reads its own.
### wai-quantum: a circuits course checked by the engine, documentation that runs, and Simplified Chinese
Someone new learns quantum computing and the toolchain from material the engine checks, in English or Simplified Chinese, and no page can drift from what the code does.
- **The circuits course** (`engine::course`, `wai-quantum/course/`): 21 lessons in eight units, from a first circuit through superposition, entanglement, interference, Deutsch–Jozsa, Bernstein–Vazirani, Grover, the quantum Fourier transform and phase estimation, noise and its mitigation, the bit-flip code and its syndrome, a Toffoli from Clifford+T and the T count, to a resource estimate.
- The lessons are data: Markdown in each locale and an `exercise.json` with a JSON Schema (`wai-quantum/schemas/exercise.v1.json`, and `course.v1.json` for the course), so the same course runs in the Studio, the desktop app and the command line.
- An exercise states its intent (an exact distribution, the same circuit or state as a program of its own, or a sampled test) and is checked with the verdict, in several cases for a generator's inputs, under the exercise's noise where it has some. Its constraints (the gates allowed, gate counts, the T count, the critique's smells, the resource estimate's counts) are measured on the program.
- What went wrong and the hints come from the reader's diagnostics, the verdict and the critique, in the reader's language with the engine's own words beside them, and never show the solution. The tests hold every solution to pass by proof or evidence, every known wrong answer to fail for its stated reason, every starter to fail, and no hint or lesson to give the solution.
- Progress is kept only where the learner is: the browser's storage, or `quantum-learn.json` beside the user's key.
- The seven-module course on interference and learning stays where it is, on the site, listed beside this one.
- **The command line**: `wai-quantum learn` lists the course and the progress, and `learn show`, `start`, `check` (exit 0 passed, 1 not yet), `hint`, `why` and `progress` work through a lesson. `--lang` (or `WAI_QUANTUM_LANG`, `LC_ALL`, `LC_MESSAGES`, `LANG`) picks English or Simplified Chinese for the command line's own words: the course, the `docs` topics and the summary of every command `--help` lists, each translation held to the English it came from and shown in English once that changes.
- **The Studio and the desktop app**: a Course pane (the lessons, a lesson's text and exercise, Load the starter, Check, Hint, What went wrong?, and a hint from a connected model with any program in its answer held back), and the whole interface's words as data in `quantum-studio/locales/`, English and Simplified Chinese; the Studio follows the browser's language until the reader picks one with the switch. `engine_course`, `engine_course_lesson`, `engine_course_check` and `engine_course_hint` in the page, the same commands in the desktop app; a course check is the same bytes natively and in wasm32.
- **Documentation that runs** (`wai/tools/quantum-docs/`): `check_docs.py` runs every block marked runnable on the pages `docs.json` names (the engine, the command line, the service, Python, the Studio, the desktop app, the editors, the providers, the compiler, estimates, verdicts, the feature matrix, the course and the translations), holds each shown output to the real one, verifies every receipt a block writes, builds the Rust blocks, and checks every block that cannot run for the commands and flags it names, every `wai-quantum …` in the prose, every relative link and every dependency shown. Its fixtures are in `fixtures/`; `ci.sh` is the check as CI runs it.
- **Drift fixed**: the crate's README pinned `"0.4"`, named an unrelated crate for the command line, documented its research tools (`entropy`, `dmrg`, `qec` and the rest) outside `lab`, and claimed the whole suite runs under wasm32-wasip2 where CI runs its pinned results and core; the command line's README had placeholders and optional flags that did not run, a history merge of versions that do not exist, and a fixed formatter check in the wrong order; the service's, the desktop app's and the editor's build steps now match what CI runs. An observable may lead with its coefficient (`"0.5 X2"`), as the command line, the Studio and the guide documented.
- **Translations**: the course, the command line's words, the Studio's interface, the main READMEs and the documentation's index in Simplified Chinese, each marked machine-assisted, to be reviewed by a fluent reader before it is advertised.
- **Index**: `wai-quantum/docs/README.md` links every page, with a place for the migration and versioning pages.
- **Schemas** (the version contract): the course as kept and as served (`wai.quantum.course/1`) and each exercise (`wai.quantum.exercise/1`), moved from `course/schema/` to the crate's `schemas/` with the rest; a check, a lesson as served and a hint (`wai.quantum.course-check/1`; a check now names its engine beside its schema); and the command line's progress file (`wai.quantum.learn-progress/1`). The docs runner reads the error-correction page, the version policy, the deprecations page and the service's self-hosting, scheduler and provenance pages too, and the index links them; the crate's README names 0.7. The command line's Chinese words cover the verbs the other branches added (`task`, `qec`, `eval`, `version`) and `submit` as it reads now, and the compile pages and the Studio's compile lens name the gate set `basis` in Chinese too.
- **CI**: `wai-quantum-docs.yml` (the docs runner, the course's tests and the command line's language tests by name, the Studio's locale check); the Studio's e2e checks the course in English and in Chinese; wai-web's engine tests, with every entry's of this release, pin 26 native and 18 wasm32.
### wai-quantum: the engine's answers as views in agent hosts, the answer view layer, correctness records and a model harness
The engine's answers drawn where an agent works, written by a model as views whose numbers the engine computes, and the model scored on them.
- **Views in agent hosts** (`engine::views`; the Model Context Protocol's extension `io.modelcontextprotocol/ui`, version 2026-01-26): both MCP servers serve six `ui://wai-quantum/*` resources (the circuit, a result with its verdict, a receipt, a critique, an estimate's frontier, a device target's map) to a host that declares the extension, and each tool names its view in `_meta.ui.resourceUri`. The job-polling tool belongs to the view (`visibility: ["app"]`) and is kept from the model. A host without the extension gets the plain text it got before, and the text of a result is the same either way.
- One self-contained page (`wai-quantum/views/`), with the view named in it: 802 495 bytes, under the extension's 1 MiB. Of that, the script is 93 783 bytes (the Studio's English words among it), the styles 49 020, and the engine 193 530 as WebAssembly (258 040 in base64) and 401 202 as JavaScript. It is built from the Studio's components by `quantum-studio/build-views.mjs`, and its manifest records each part's size and SHA-256 and every source's.
- The receipt is checked inside the view, by the engine's own `job::verify` built with no imports (`wai/wai-quantum-views`). The extension's default content-security policy refuses to compile WebAssembly, as measured in a headless browser, so the page also carries the engine translated to JavaScript and uses whichever runs.
- The page builds byte for byte the same in a clean CI image on x86_64 Linux. The engine's bytes change with any change to `wai-quantum`, and between macOS and Linux, so CI holds the script and the styles to their sources and warns when the engine needs a rebuild.
- A scripted host (`quantum-studio/test/views.test.mjs`) runs against both servers in a headless browser: it lists, reads and draws every view, verifies the receipt in the view in both forms, refuses a changed one, and holds every page to the limit.
- **The answer view layer** (`wai/extensions/quantum-answers.md`, `wai.quantum.answer/1`, a draft; `engine::answer`): fourteen views and eleven engine calls written beside an OpenQASM 3 program, one statement a line. Every number a view shows comes from an `@` call that the host carries out.
- A streaming reader (`Stream`) gives each statement as it arrives, and the same answer however the text is split.
- `@Seal` seals the calls. A stored answer carries its receipt, which is checked by running the answer again.
- Codes `J100` to `J112`, each with its outcome (dropped, defaulted or warned), sit beside the reader's `Q` codes (`docs/job-refusals.md`).
- Conformance (`quantum-conformance/answers/`): twenty cases covering every code, run natively, under `wasm32-wasip2` and in the browser build.
- The token cost is measured and recorded (§9). On the harness's fourteen tasks, in the vocabularies of three open-weight models, the layer takes 29 to 32 percent more tokens than a job's JSON for the same request. The cost is per statement (24 to 28 tokens for one sample and one verdict), and noise that two calls share is named once. The whole layer is 2.7 to 2.8 times the program alone.
- Surfaces:
- wai-web: `AnswerStream`, `engine_answer_parse`, `engine_answer_prompt`, `engine_answer_verify` and `engine_answer_marker`, with `answer_value` and `answer_evaluate` on the page's engine;
- the Studio's answer pane, which draws each statement as the assistant streams it, runs its calls and seals the answer;
- the desktop app's `answer`.
- **Correctness records** (`engine::telemetry`, `wai.quantum.telemetry/1`): off by default. Turned on in the Studio, it keeps one record per answer in the browser: the codes, the verdict level, the repairs, whether it was accepted, and the receipt id. Nothing is sent anywhere, and the records leave only when exported. The failures export as an eval corpus in the intent conformance format. A test shows that nothing is kept while it is off.
- **A model harness** (`wai-quantum eval run` and `eval corpus`; `quantum-conformance/eval/`):
- Fourteen tasks from the intent conformance set and the worked examples. Each has a reference program that a test holds to its intent by proof.
- It asks a model at temperature 0, on this machine or a private network (any other host is refused unless allowed), for a program or for an answer in the layer. Optional repair rounds send the engine's codes back.
- Each answer is scored as valid, and as solved with its verdict level. Joules per solved task are reported where the model server's process energy can be read (macOS).
- A report ranks nothing and is not committed.
- One open-weight model on the build host (35 billion parameters, 3 billion active, 5-bit, temperature 0), measured in §9.1 of the spec:
- asked for the program, 12 of 14 answers were valid and 11 solved, and 13 and 12 after one repair round;
- asked for the layer, 4 were valid and 3 solved, and 7 and 6 after one repair round.
- Its errors are its own: an unnamed fence, a view given a call where it takes a program, undeclared names, and arguments written as `name=value`.
- The prompt now gives each argument's kind and the rules those errors broke. The reader answers `name=value` by saying that arguments go by position.
- **Schemas** (the version contract): the answer layer's documents (`wai.quantum.answer/1`: an answer read, carried out, a seal's request and result, a stored answer checked; labelled a draft whose form may change before the extension settles), a correctness record and its corpus (`wai.quantum.telemetry/1`), the views page's manifest (`wai.quantum.views-bundle/1`), and the harness's report, corpus and token counts (`wai.quantum.eval-report/1`, `wai.quantum.eval-answers/1`, `wai.quantum.token-cost/1`, the command line's). The view's own tool `poll_quantum_job` answers to the job's schema. The views name a compile's gate set by its basis.
- **The engine**: the order of a program's final measurements makes no difference. Equivalence, the verdict's reference check and the rewriter compare which qubit each bit reads, not the order the measurements were written in. The harness found this: a correct GHZ state measured in the reverse order was refused.
- **CI**:
- `wai-quantum-views.yml`: the crate, the page against its sources, and the scripted host on `macos`;
- the answer set under `wasm32-wasip2` in `wai-quantum-verdict.yml`;
- the eval and views tests in `wai-quantum-cli.yml`;
- the telemetry test, the page check and the desktop app's answer test in `wai-quantum-studio.yml`;
- wai-web's test pins (with every entry's of this release, 26 native and 18 `wasm32`).
### wai-quantum: error correction and mitigation, completed and on every surface
Codes with certified distances, decoders each held to the exhaustive ground truth (every error below half the distance corrected to a trivial residual), the error-correction texts read and written as they are, and mitigation in a job with its error bars and assumptions (`wai-quantum/docs/qec.md`).
- **Colour codes** (`quantum_color`): the triangular 6.6.6 codes of odd distance, `[[(3d² + 1)/4, 1, d]]`, decoded by restriction to matching on the three restricted lattices, lifted at each colour with both logical classes kept, and completed below half the distance by an exact search; every error below half the distance corrected, checked to `d = 9`. Matching alone is shown missing 16, 16 and 10 of the 190 errors of weight at most two at `d = 5`.
- **Distance certificates** (`quantum_distance`): an exhaustive search over connected supports, symmetries checked before use (a bivariate-bicycle code's translations), each sector's distance with a witness, `no_logical_below` and the node count, re-checked node for node (`wai.quantum.distance/1`); surface, toric, Steane, colour and bivariate-bicycle codes to 144 qubits certified in the tests, `[[288, 12, 18]]` in 18 s.
- **Correlated matching** (`quantum_match::CorrelatedMatching`): exact matching twice through a model's decomposed faults, the second answer kept only when the model's mechanisms explain it more cheaply; fewer failures than exact matching on the same shots (338 against 440 at `d = 5`, `p = 0.006`, 20,000 shots), and on every set of one or two faults of the `d = 5`, two-round experiment it misses 73 pairs where matching misses 103. The circuit-level ground truth for matching: every edge set below half the graph's distance corrected.
- **Sliding-window decoding** (`quantum_window`): a stream decoded as rounds arrive, `commit` rounds committed and `buffer` rounds decided again, with a stated latency budget per round and each window's time measured on the machine and labelled so; a window that covers everything is the batch decoder on every shot, and every edge set below half the distance is corrected once the buffer is `(d + 1)/2` rounds.
- **The error-correction texts** (`quantum_qec_text`): the ecosystem's circuit text with tags, Pauli-target observables, coordinates and `REPEAT`, and the detector error model text with `^` decompositions, declarations, `repeat` and `shift_detectors`, read as written and written back; a circuit's model keeps its tags and coordinates. Conformance vectors (`quantum-conformance/qec-text/`) hold every form, the model each circuit gives and each refusal (`tests/qec_text_conformance.rs`).
- **Mitigation in a job** (`engine::mitigate`, `"mitigate"` beside `"noise"`): zero-noise extrapolation by folding (Richardson, linear or exponential), error amplification and probabilistic error cancellation from a sparse Pauli–Lindblad model learned by simulated benchmarking, Clifford data regression, virtual distillation, and readout inversion. Each value raw and mitigated with its error bar, beside the ideal one the simulation knows, the method's options and its stated bias and variance assumptions, sealed with the job; refusals under `J008`.
- **Twirling and decoupling as compile passes** (`engine::compile`, `"twirl": SEED`, `"dd": "xx" | "xy4"`): each two-qubit Clifford gate framed by a seeded Pauli and its image, each distinct frame proved the gate by the equivalence ladder; decoupling pulses in each idle window, the sequence proved the identity; the whole circuit checked by the ladder where it decides.
- **One error-correction call for every host** (`engine::qec`: `code`, `decode`, `threshold`), refusals under `J009`, each answer sealed in a job's form:
- the command line: `wai-quantum qec code|decode|threshold`, `run --mitigate`, `compile --twirl --dd`; the research forms stay at `wai-quantum lab qec`;
- both MCP servers: `qec_code`, `qec_decode` and `qec_threshold`, and `mitigate` on runs and `twirl`/`dd` on compiles; the generated agent docs list them;
- the service: `POST /v1/qec/code`, `/v1/qec/decode` and `/v1/qec/threshold`, bounded here (`J031`, with the command that runs it locally);
- the Studio: an error-correction pane (the lattice, a syndrome made by flipping qubits, the correction and its residual, the certificate's witness, a threshold plot) and mitigation in the Noise section; the browser engine (`qec`) and the desktop app (`qec`);
- Python: `qec_code`, `qec_decode`, `qec_threshold`, and `run(..., mitigate=...)`.
- **Schemas** (the version contract): an error-correction answer (`wai.quantum.qec/1`: a code, a decode or a threshold, each naming the engine and its schema inside what its receipt seals), a distance certificate (`wai.quantum.distance/1`) and a job's mitigation (`wai.quantum.mitigation/1`); the job request names `mitigate`, and a compile's options `twirl` and `dd`. `qec_code`, `qec_decode` and `qec_threshold` answer to their output schemas on both servers, and the strict tests call each, a mitigated run and a compile with both passes. Mitigation's refusals are `J008` and error correction's `J009` (`J007` is a set that does not read).
- **Not done:** a magic-state cultivation circuit generator; its non-Clifford stages would need state-vector checks against a reproduced reference.
- **CI:** `wai-quantum-qec.yml` runs every new module's tests and the text conformance in release, each named test required to pass; `wai-web-wasm.yml` requires the browser engine's error-correction and mitigation tests by name, natively and under wasm32.
### wai-quantum: a durable job service with sets, sweeps and dry runs, run yourself
The job service keeps what it answered across its restarts, runs many programs as one job, says what a job would take before it runs, and can be run on your own premises from an image (`wai-quantum-service/docs/self-host.md`). The free hosted service still takes no account and no key.
- **A durable store** (`wai-quantum-service/src/store.rs`): programs, requests, results and receipts kept under the BLAKE3 digests of their bytes, behind a `Store` trait, with a files store (each file written whole, synced and renamed, under a lock) and a memory one. Every job is in the store before its submission is answered, and at each change of its status. On start the service reloads it: finished jobs served as they were, their receipts verifying; a job that was running comes back `interrupted`, with the reason; queued ones queued again. Retention stays configurable (`WAI_QSVC_RETAIN_SECS`, a day by default). A record names its client by a keyed hash, never its address. A worker whose service has gone stops itself. A test kills the service mid-job and restarts it (`tests/restart.rs`).
- **Sets and sweeps** (`engine::sets`): `"programs"`, a list of programs or jobs, or `"sweep"`, values for the program's integer, float and angle inputs (every combination; the reader binds a real-valued input with `qasm::bind`). At most 100 items, each a plain job with its own result and receipt; the set's receipt seals its head, which holds the Merkle root of every item's leaf under the crate's Merkle domains, and each item carries its path. `POST /v1/verify` and `wai-quantum verify` check a set whole. `J007` is a set that does not read.
- **Dry runs**: `"dry_run": true` returns the method, memory, estimated time and energy (each with its basis: this service's own timings and readings, or the planner's nominal rate, `catalog::nominal_rate`), the bounds that apply, where the client stands, and whether the service would take the job now, or the refusal it would give. It runs nothing.
- **Status and limits**: `GET /v1/jobs/{id}/events` (server-sent events to the job's end) and `?wait=S` on the poll; every answer carries `RateLimit-Policy` and `RateLimit`, and a refusal past the limits `Retry-After`.
- **Receipt keys**: the key set at `/.well-known/wai-quantum-receipt-keys`, each key with its validity window; `wai-quantum-service rotate-key` retires a key with a transition statement both keys sign, and the old key stays published.
- **Energy on Linux** (`engine::energy::Packages`): the processor packages' counters, opened only when every package zone reads and is live, the figure the packages' energy over the job's window with no attribution to the job and the jobs beside it named, its uncertainty covering the two reads and the update edges measured at start; otherwise `unmetered` with the reason (no interface, no privilege, not live), never zero.
- **Provenance** (`engine::run::run_with_provenance`, `wai-quantum-service/docs/provenance.md`): each new result carries the program's digest, the method, each pass its compile ran with its check's verdict and the certificate's digest, the shots, seed and plan, and the engine's and host's versions, mapped to the categories provenance surveys use. Results without it verify as they did, and the engine's own results are unchanged.
- **Self-hosting**: an OCI image (`wai-quantum-service/deploy/Containerfile`: two stages, each base pinned by digest, a locked build with paths remapped and the timestamp fixed, an unprivileged user) and `deploy/check-image.sh`, which builds it, runs it, holds it to the smoke checks and removes it, pushing nothing. Optional API keys for a private deployment (`WAI_QSVC_API_KEYS`, off by default; `J033`), counted by key, stored as digests.
- **A scheduler's resource**: `wai-quantum task` (target, acquire, start, status, wait, result, stop, release), the interface a batch scheduler's prologue, job and epilogue call (`wai-quantum-service/docs/scheduler.md`).
- **Surfaces**: the service's MCP tools `run_quantum_set` and `dry_run_quantum_job`; the command line's `--program` and `--sweep` on `run`, `plan` and `submit`, and `submit --dry-run`, `--detach` and `--api-key`, `submit` following the job's status; the OpenAPI document, both smoke tests, the READMEs and the generated agent docs.
- **Schemas** (the version contract): a set's result (`wai.quantum.set/1`, the head named as the engine's, inside what its receipt seals), a result's provenance (`wai.quantum.provenance/1`; its compilation names the gate set compiled to as `basis`), the service's receipt key set (`wai.quantum.receipt-keys/1`, with its signed transitions) and the command line's scheduler lease (`wai.quantum.lease/1`). The job request names `programs`, `sweep` and `dry_run`, a job's result may be a set's, an energy figure may be the processor packages', and a job names its store digests. `run_quantum_set` and `dry_run_quantum_job` answer to their output schemas, each with its verdict, refused or not, and a run asked as a dry run answers as one. The service keeps its store in `~/.local/share/wai-quantum-service/store` unless `WAI_QSVC_STORE` names another (or `memory`); every CI leg and test that starts it names one in its own scratch.
### wai-quantum: the version and deprecation contract, schemas as data, and versions in what is written
Anyone building on WAI Quantum can read what is stable and for how long, and is told by the tools before anything they use changes.
- **The policy** ([`wai-quantum/VERSIONING.md`](wai-quantum/VERSIONING.md)), linked from every WAI Quantum README: the public surface (the engine's documented API, the command line's verbs, flags, exit statuses and `--json`, the job JSON, both MCP servers' tools and schemas, the receipt, the job service's API, the Python package, the provider protocol, the editor extension's settings, the grammar's node names), what each part of a version number promises, the support windows the owner decided on 2026-10-27 (at least a year between majors, six months of fixes for the previous major, deprecation at least one minor release before removal), how a deprecated item says so on each surface, and the rule before 1.0 (the first non-zero component is the major; the windows begin at 1.0). [`DEPRECATIONS.md`](wai-quantum/DEPRECATIONS.md) lists each deprecated item with its replacement; none today.
- **Schemas as data:** versioned JSON Schemas (2020-12), `wai.quantum.<document>/<major>` kept as `<document>.v<major>.json`: the job request, the job result, the job, the receipt (its second form; the first still verifies), the plan, a compile with its certificate, the engine's tools' answers (the reader's diagnostic codes among them), the versions a host reports (`wai-quantum/schemas/`); the local MCP server's own tools' answers (`wai-quantum-cli/schemas/`), the job service's (`wai-quantum-service/schemas/`), and the provider protocol's messages (`wai-quantum-qpu/schemas/`). `engine::schema` checks a document against them; the tests also refuse a member a schema does not name, so a change to what the code writes fails until its schema changes with it.
- **Every MCP tool says what it answers:** each tool of both servers carries its answer's schema as its `outputSchema`, standing alone. A test calls every tool and holds its answer to it. The local server's tool list is committed (`wai-quantum-cli/docs/local-mcp.json`) beside the service's, both held to the live lists.
- **Versions in what is written:** every result names the engine that made it and the schema it follows (`"engine": {"name": "wai-quantum", "version": …}`, `"schema": "wai.quantum.job-result/1"`), inside what its receipt seals. The receipt's form did not change; the canonical text of a new result holds two more members. Two job bodies sealed by the engine as it was before (a run here and a run on a provider) are kept with their digests pinned, and still verify.
- **Versions reported:** `wai-quantum version [--json]`; both MCP servers in `initialize` under `_meta["science.transaction.wai/versions"]`; the job service in `GET /v1` and `quantum_service_info`. The agent docs (llms.txt, llms-full.txt, the `AGENTS.md` block, the Agent Skill) say how to read them and where the policy is.
- **CI:** `wai-quantum-contract.yml` on Linux: `cargo-semver-checks` 0.51.0 against wai-quantum 0.6.16 and wai-quantum-cli 0.1.4 (it lists every breaking change, and fails only on one made without a major bump), the schema tests by name, the policy linked from every README, and the deprecations page's table.
### wai-quantum: a verdict for every answer, stable refusal codes, and repairs held to the flagged lines
Every answer an agent or a person gets about a program states one verification level, so "valid" is never the claim (`wai-quantum/docs/verdicts.md`).
- **The verdict** (`engine::verdict`): the level of an ordered ladder (`refused` < `parsed` < `executed` < `evidence` < `proof`), whether the intents hold, and the level in one plain line; with what the reader said (its codes), the method and its accuracy class, each intent's check, the critique's findings and the repairs made.
- Intents: an exact distribution with its total variation distance (the state vector, or under noise the density matrix), the same circuit as a reference by the equivalence ladder (proof or evidence), the same state from |0…0⟩, or samples of the run held to a stated distribution by a stated test at a stated level (the debugger's `sampled` intent, now in the engine for every surface: evidence, never proof). `"require": "proof"` counts evidence as unchecked.
- A run on a device target states its verdict too: its intents checked on the program, a sampled intent on samples of the target's run.
- A job asks with `"intent"` or `"verdict": true`, and its result carries the verdict, sealed by the receipt; the plan admits the check's memory. A job that asks for neither gives the bytes it gave before (pinned against the engine before this change).
- The checker `wai-quantum test` used moved into the engine, and `test` now runs the program with its intents; `--junit` reads the engine's verdict.
- **Stable refusal codes for what is asked of a program** (`engine::refusal`, `docs/job-refusals.md`): the request (`J001`–`J006`), the method (`J010`–`J012`), intents and repairs (`J020`–`J022`) and the host (`J030`–`J032`), each refused or retry, beside the reader's `Q` codes. A refusal ends with its code, a reader's cause before it (`[Q500] [J011]`); the service's HTTP refusals carry it as `code`.
- **Repairs** (`engine::repair`): the fixes that are mechanical, each on the lines its diagnostic flags (a missing `;`, an undeclared register's size, a measurement with no target, an unbounded `while` given its bound, a non-dyadic angle rounded to the bits chosen with its rounding stated); a proposal that changes any other line rejected (`J021`); what needs a person returned with the reader's advice. A job may ask to be repaired first.
- **Surfaces**:
- the command line: `run --intent FILE` (a reference program, a distribution, intents, or `intent.json`), `--verdict`, `--repair`, and `repair FILE` (`--proposed`, `--max-iterations`, `--dyadic-bits`, `--size`);
- both MCP servers: `intent` and `repair` on `run_quantum_circuit`, which always states its verdict; the tool `repair_qasm`; the verdict in every answer about a program and the codes in every refusal; versioned output schemas on every tool; the resource `wai-quantum://docs/verdicts`; the rules and prompts point agents at the verdict and `repair_qasm`, and the generated agent docs and the Agent Skill say so;
- the service: intents and repairs in jobs, held to the equivalence bound; `POST /v1/repair`;
- the Studio and the desktop app: a badge with the level on every run and every assistant proposal;
- the browser engine: `engine_verdict` and `engine_repair`;
- Python: `run(..., intent=...)`, `Job.verdict`, `wai_quantum.verdict` and `wai_quantum.repair`.
- **Conformance** (`quantum-conformance/intents/`): programs, their intents and the verdict each must get, every level covered and the distances known by hand, run natively and under `wasm32-wasip2` (`wai-quantum-verdict.yml`) and in the browser build (`wai-web-wasm.yml`).
### wai-quantum: resource estimation from programs
What a fault-tolerant machine would need to run a program, derived from the program itself, calibrated from the crate's own decoders, and sealed (`wai-quantum/docs/estimate.md`).
- **Logical counts** (`engine::estimate::count`): the program written in Clifford gates, `T`, `z` rotations, Toffolis (one CCZ state each) and measurements:
- a gate under several controls goes through clean ancillas, a ladder of Toffolis computed and uncomputed: `x`, `y`, `z` or `h` under `c ≥ 3` controls takes `2c − 3` Toffolis and `c − 2` ancillas, a phase under `c ≥ 2` takes `2c` and `c`;
- each rotation synthesised at its own angle within its share of the budget's synthesis share, its `T` count the word's (`engine::synth::rz`);
- `T` gates, Toffolis, rotations and the `T` in their words, measurements, non-Clifford, rotation and `T` depth, total depth, and logical qubits with ancillas;
- a program that branches is counted step by step, each step once, an upper bound it says it is.
- **Ground truth**: a Bell pair, `T` chains in series and in parallel, the ripple-carry adder (`2n` Toffolis), the QFT (`3(n−1)` `T`, `3(n−1)(n−2)/2` rotations); every ladder held to the gate it replaces on dense unitaries with its ancillas at `|0⟩`; each rotation's `T` count equal to the synthesiser's.
- **The physical estimate**: the planar model (`quantum_resource`) on those counts, a rotation layer taking as many steps as its longest word has `T` gates; the headline and the frontier of estimates no other beats on both physical qubits and runtime.
- **Calibration** (`engine::estimate::calibration`): on gate-based qubits the surface code's `P(d) = a·(p/p*)^((d+1)/2)` is fitted to the memory experiment decoded by exact matching (`a = 0.0233`, `p* = 0.01004`) or by union-find (`a = 0.0214`, `p* = 0.00952`) on the same shots: `d` 3, 5, 7 by `p` 0.002 to 0.005, 100 000 shots (25 000 at `d = 7`), seed 1. The table is in the crate, a release test measures every point again, and the example `estimate_calibration` prints it. The planar model's assumed constants remain available; an extrapolation past the measured range is flagged.
- **Receipts**: every estimate a resource-estimate receipt whose job binds the program's digest, every count, the budget, the synthesis share and each rotation's (a new counted-program form; the earlier forms hash as before); the frontier's name the headline's as parent; the answer sealed in a job's form. `engine::estimate::verify` checks the seals and re-runs the request.
- **Interchange**: the logical-counts JSON (`numQubits`, `tCount`, `rotationCount`, `rotationDepth`, `cczCount`, `ccixCount`, `measurementCount`) and the routine format (`v1`: routines, ports, resources, children, connections), each read and written, the routine held to its schema, with a self-made fixture. A document's rotations cost the most `T` any of eight reference angles needs.
- **Refusals** carry codes `QE02` to `QE08`, beside the reader's own.
- **Surfaces**:
- the command line: `wai-quantum estimate prog.qasm` (`--budget`, `--qubit NAME|all|FILE`, `--calibration`, `--max-slowdown`, `--counts`, `--routine`, `--export`, `--json`), and `verify` re-running an estimate; `lab estimate` unchanged;
- the service: `POST /v1/estimate`, within bounds (200 000 gates, 256 distinct angles, `1e-12` a rotation), and `POST /v1/verify` re-running an estimate;
- the MCP tool `estimate_resources` on both servers, with one description and schema, and the verify tools re-running an estimate;
- `QuantumEngine.estimate` and `engine_estimate_verify` in a page; `estimate` in the desktop app;
- the Studio's Estimate pane: the counts, the estimate, the frontier plotted on log scales and listed, the seals verified and the request re-run in the page;
- Python: `wai_quantum.estimate` and `verify_estimate`.
- **Determinism**: CI compares an estimate natively and as a WASI component byte for byte, and the browser build's tests estimate one request to the native build's pinned digest. The estimate's tests run under `wasm32-wasip2`, and `wai-quantum-estimate.yml` runs them in release with the whole calibration.
### wai-quantum: device targets and calibrated noise
The engine knows real devices: their qubits, pairs, native gates, durations and errors. A program is compiled, laid out and simulated as it would run on one.
- **The model** (`engine::targets`): each qubit's T1, T2, frequency, readout assignment errors and measurement time; each native gate where it runs, with its error and duration; the coupling map; and the calibration snapshot's id and time. It is one strictly read document, `wai.quantum.target/1`, with a JSON Schema, and the BLAKE3 hash of its RFC 8785 text names the snapshot in a receipt.
- **Importers:** the per-qubit and per-gate calibration properties a superconducting-device service publishes (alone or beside its configuration, the form published snapshot devices ship in), an instruction-properties target, the device-capabilities document a cloud service returns, and the open provider protocol's `describe`. Four built-in targets, all synthetic, one in each shape: a 5-qubit line, an 8-qubit ring, a 27-qubit heavy-hexagon lattice and 5 qubits all coupled.
- **Noise from a target**, per qubit and per pair. Each gate's reported error becomes the depolarising channel that, with its relaxation, has exactly that average infidelity. T1, T2 and the durations give amplitude damping and dephasing, idling included, and readout comes from the assignment matrix. The density matrix and the branching trajectories take it as a schedule of channels (`quantum_noisy::Schedule`). The global noise model keeps its bits.
- **Noise-aware layout and routing:** many layouts and their most reliable routes are compared, and the highest expected success is kept. The compiler's own choice (the fewest swaps) wins unless beaten, and the program is checked against the circuit. `native::compile_at` and weighted routes are new; `native::compile` is unchanged. A provider whose `describe` states its calibration gets its programs placed the same way, in the device's own gates and checked before they are sent, and the receipt says how. The provider protocol now states the `durations` field's form.
- **Job JSON:** `target` is a document or a built-in target's name. The plan states the layout, its expected success beside the fewest swaps', and the noise derived. The result carries the same with the target's hash, inside the receipt, and the distance of its distribution from the circuit's ideal one.
- **Surfaces:**
- the command line: `wai-quantum targets import|list|show|remove|schema` (import also from a provider's describe) and `run --target`/`plan --target`;
- MCP on both servers: `list_targets`, `show_target`, `run_on_target`, and `target` in the run tools' schemas, with the agent docs regenerated;
- the service: `GET /v1/targets`, `target` in the OpenAPI document, and a worked example (it reads the document given and fetches nothing);
- wasm: `engine_targets`, `engine_target` and `engine_target_place`;
- the desktop app: the same three, the person's kept targets included;
- the Studio: a **Device** section with the coupling map coloured by each pair's and each qubit's error, the chosen layout drawn on it, a snapshot imported, and the result's **Device** block.
- **Checked against ground truth:**
- a target with no errors gives the ideal distribution to 1e-12 in each of the three gate sets;
- T1 decay, T2 coherence and the assignment matrix give their closed forms;
- each gate's channels have exactly its reported average infidelity, by its Choi state;
- the layout equals an exhaustive search on a line;
- a run on a target gives the same bytes natively and under wasmtime (a CI check).
- **Not done:** a cross-vendor C device interface is a library with no document form to import; a host that loads one answers the provider protocol's `describe`, which is read. Programs with angles that are not dyadic are refused on a target, since the compile is exact. Crosstalk, leakage, coherent errors and drift are not modelled.
### wai-quantum: a debugger, statistical tests, state views and editing by the drawing
What a program does, step by step, with every number the engine's: in the Studio, in editors, from the command line and from agents.
- **The debugger** (`engine::debug`): a program stepped forward and back one gate, measurement or reset at a time on a method where every number is exact for it: the bit-exact state vector when every angle is a dyadic multiple of 2π, else double precision, and under noise the density matrix beside the noiseless state on the same path.
- Breakpoints by line, a measurement's line found through broadcasts, loops and branches.
- Probes at any position: the amplitudes with their phases, the probabilities, each qubit's Bloch vector, purity and probability of 1, the reduced density matrix of chosen qubits, and the entanglement across a cut (the entropy of a side of a pure state, or the logarithmic negativity of a small mixed one).
- Every branch at the measurements in the middle, with its weight (exact fractions by the stabilizer tableau for a noiseless Clifford program) and the distribution of the final measurements on it; a recorded path replayed, and an outcome the state cannot give refused.
- Deterministic: stepping back runs again from a saved state, so a state seen twice is the same bits, and the exact method's state after the last gate is the exact run's, by its `state_hash`. Refused past 16 qubits, or 8 under noise. Its tests are ground truth: teleportation's four branches a quarter each, a Bell pair's reduced states, the noisy steps against the density matrix's run.
- **Statistical intents** (`engine::stats`, `wai-quantum test`): `{"sampled": …, "test": "chi_squared" | "g_test", "alpha": …, "shots": …}` runs the program for the stated shots and holds the counts to the distribution by the stated test at the stated level, as evidence; the p-value is the same bits on every platform. `test --junit FILE` writes the verdicts as a JUnit report. The exit codes are as they were.
- **The command line:** `wai-quantum debug` prints a position's state or every branch; `wai-quantum dap` serves the debugger over the debug adapter protocol (launch, breakpoints, steps both ways, continue both ways, the probes and the branch tree as variables), driven in `tests/dap.rs` as an editor drives it; `draw --svg`, `--latex` (plain TikZ) and `--describe` (in words).
- **Agents:** the stdio MCP server gains `debug_step` and `debug_branches`, read-only and deterministic, and `draw_circuit` takes a `format`; the agent docs are regenerated from the live lists.
- **Editors:** the extension contributes breakpoints and a debugger whose launch settings are the adapter's own, held to its source by `test/check.mjs`; the Lua and `languages.toml` configurations register it.
- **The Studio:** "Step through", with a slider, the branch tree, each qubit's phase disk with a purity ring and its Bloch sphere in plain SVG, the amplitude and phase table, the reduced state of chosen qubits, and under noise the noisy state against the noiseless one; editing by the drawing (a gate dropped on a wire, dragged or moved and deleted from the keyboard), each an edit the engine makes in the program's text through its syntax tree (`engine::visual`) and recorded as the reader's; the circuit described in the engine's words for a screen reader (`engine::describe`); the drawing saved as SVG or LaTeX (`engine::latex`). The same in the desktop app, natively.
- **CI:** the editors workflow runs the debugger's module tests and the adapter's sessions on Linux and macOS; the wasm workflow's counts are now 16 native and 9 under Node.
### wai-quantum: synthesis and certified compilation
Circuits synthesised and optimised by the engine, every compile carrying a sealed certificate of how close the output is to the program and how that was established (`wai-quantum/docs/compiler.md`).
- **Rotation synthesis** (`engine::synth::rz`): `Rz(θ)` as a Clifford+T word within a requested `ε`, by the Ross–Selinger method:
- the ε-region and the unit disk as a two-dimensional grid problem over `Z[ω]`, made upright by a grid operator (rotations, shears, `K` and shifts by powers of `1 + √2`), each candidate tested in exact arithmetic;
- the norm equation `t†t = ξ` solved prime by prime in `Z[ω]`, or shown to have no solution;
- the operator written in its Matsumoto–Amano normal form, the fewest `T` gates of any word for it;
- the word multiplied back exactly, and its distance to the rotation recomputed from the word in fixed point and rounded up: that is the distance reported.
- `T` counts follow `3·log₂(1/ε)` (99 to 101 at `1e-10`). Against every Clifford+T operator with at most 11 `T` gates, 21 of 24 cases land on the optimum and the rest one above.
- **The arithmetic under it**: integers of any size (Knuth division, square roots, a primality test, Pollard–Brent factoring within a step budget), fixed point with 320 fractional bits with `π`, `√2`, sine and cosine, and the rings `Z[√2]`, `Z[ω]` and 2×2 matrices over `D[ω]`. Each held to a ground truth: 128-bit arithmetic, every `t` with small coefficients for the norm equation, brute force for the grid problems.
- **One and two qubits**: ZYZ Euler angles of any one-qubit unitary, and the KAK decomposition of any two-qubit unitary into at most three CNOTs (two or none when its canonical coordinates allow), with a deterministic Jacobi eigen-solver.
- **Phase folding** (`engine::synth::phase`): phase gates on one parity merged, checked by recomputing the phase polynomial.
- **The pass pipeline** (`engine::compile`):
- passes `lower`, `simplify`, `phase_fold`, `resynth_1q`, `resynth_2q` and `approximate`, levels 0 to 2, bases (the gate set compiled to) `clifford_t` and `standard`;
- each pass checked where it acts, and a pass whose check fails not applied;
- one certificate per compile: each pass's counts before and after (gates, CNOTs, `T`, rotations), its check and verdict (`proof`, `exact` or `approximate` with its distance), the `ε` budget, its share per rotation and the bound the distances add to;
- up to eight qubits the whole output is also measured against the whole program on dense unitaries;
- sealed in a job's form, so the existing receipt check verifies it;
- with `"stages": true`, the circuit after each pass as well.
- **Compiled jobs**: a job with `"compile": {"epsilon", "level", "basis"}` runs its program compiled first, bit-exact for Clifford+T, and its result's `compiled` states the distance.
- **Surfaces**:
- the command line: `wai-quantum compile` (`--epsilon`, `--level`, `--basis`, `--json`, `--certificate`) and `run --compile ε`;
- the service: `POST /v1/compile`, sealed with the service's key, within bounds (20 000 gates, 256 rotations, `1e-12` a rotation; past them 422 with the local command);
- the MCP tool `compile_qasm` on both servers, and `compile` in the job schemas;
- `QuantumEngine.compile` in a page, and the desktop app's `compile` command;
- the Studio's compile lens: each pass with its counts and check, the certificate verified in the page, and the circuit after any pass drawn.
- **Determinism**: CI compiles one program natively and as a WASI component and compares the sealed bytes, and the browser build's tests compile one request to the native build's pinned digest. The synthesis tests also run under `wasm32-wasip2`.
### wai-quantum: one OpenQASM 3 front end, every refusal and warning under a stable code
One reader, with the source position of everything it handles, for every surface: the job service, the command line, both MCP servers, the Studio, the desktop app, the edge component and the in-browser IDE.
- **Diagnostic codes** (`qasm::diag`): every refusal and warning the reader gives, and every construct it lowers, carries or keeps verbatim, has a code with its outcome (refused, lowered, carried, preserved, warned) and a one-line meaning. A diagnostic's text keeps the message people read and ends with its code (`line 3: unknown gate foo [Q103]`); as data it has its line, column and length. The table is `wai-quantum/docs/qasm-diagnostics.md`, which a test holds to the code's table, and is served by `wai-quantum diagnostics [--json]`, the `qasm_diagnostics` tool of both MCP servers and `engine_diagnostic_codes()` in the browser engine. A code never changes meaning once released. `lint` gives the code, the column and the length, and the warnings.
- **The language** (`qasm::lex`, `qasm::parse`, `qasm::elab`): `def` subroutines, inlined (classical arguments by value, qubits by reference, values returned, a returned measurement written into the caller's bit, bits of their own fed forward); `inv @`, `pow(k) @` and exact fractional powers, and `ctrl`/`negctrl` on any gate, user gates included, each lowered exactly and held to the matrix it must equal; `float` (16, 32 and 64 bits), `angle[n]` (held to n bits of a turn, so dyadic, with the rounding reported), `complex`, `uint`, `bool`, bit registers set by constants, arrays, casts and the built-in functions, computed with the crate's own functions; `while` (unrolled when its condition is known, run at most `@wai.max_iterations N` times on measured bits), `switch`, `break`, `continue`, `end`; conditions on measured bits with comparisons, `&&`, `||` and `!`, compiled to forward jumps and checked against every value of their bits; `output`, carried into the result; `delay`, `box`, `stretch`, `barrier` and gate durations, carried as timing and labelled in a result whose method applies no durations; `defcal`, `cal` and `defcalgrammar`, kept verbatim and never run; aliases, slices, index sets, physical qubits, `gphase`.
- **One grammar**: `quantum_toolchain::from_qasm` (the edge component's and the processor clients' import) reads through the same reader and applies the bit-exact circuit's restriction, refusing with the reader's codes (Q500 a non-dyadic angle, Q501 width, Q502 a dynamic program). A controlled gate the modifiers write stays one gate under all its controls, so `to_qasm`'s output reads back gate for gate. A new feature, `quantum_lang`, holds the reader without the lowerings.
- **A job's result** carries `outputs`, a `timing` label (`applied: false`, Q400) and the reader's `diagnostics`, each only when there is one, so a program with none has the result it always had.
- **Conformance** (`wai/quantum-conformance/qasm3/`): the specification's 21 example programs, unchanged, and 38 programs for the constructs with references, each with what the reader must do (an exact distribution or outcome, equivalence to a reference, or a code). `stdgates.qasm` holds every standard gate to its definition in `U` and `CX`. Run natively and under wasm32-wasip2 (`wai-quantum-lang.yml`) and in the browser build (`wai-web-wasm.yml`: wasm32 count 8 to 9, native `quantum_engine::` count 14 to 15).
- **Feature matrix**: `wai-quantum/docs/qasm-feature-matrix.md`, every construct of OpenQASM 3.1 with what the reader does with it and its code.
- **Changed refusals**, each now read: `inv @`, `pow @`, a control on `rz`, `while`, two registers and gate definitions in `from_qasm`; a loop's integer sharing a gate parameter's name; `^` between integers under `OPENQASM 3` (exclusive or). A bare radian number in `from_qasm` is now held to the reader's dyadic tolerance, where a number within 1e-9 of a dyadic angle was rounded to it.
### wai-quantum: the engine in editors, a language server, a formatter, a grammar and an extension
The engine where programs are written: any editor that speaks the language server protocol gets its diagnostics, its matrices and counts, and its runs.
- **The language service** (`engine::editor`), pure and with no protocol in it:
- diagnostics: the reader's refusal on its line (one function, `reader_diagnostic`, maps the reader's errors, so stable codes and exact spans plug in there), and the critique's smells on theirs, their kind as the code; a generator read at the given inputs, or 4;
- completion by what the cursor's place takes (qubits after a gate's name, values in its parameters, integers in an index, bits after `->`), the standard gates with placeholders, snippets, and sample programs in an empty file;
- hover: each standard gate's matrix written exactly, held entry by entry to the engine's own under every name the reader accepts; a user gate's matrix from the engine's matrices of its gates; each gate's cost as the engine counts it (two-qubit gates, T count, depth, Clifford), at the call's angles or generic ones, said which; a register's size;
- definitions, references, highlights and renames over an index of declarations and scopes that reads all of OpenQASM 3 and a half-typed program; an outline; folding (blocks, comments, held blocks);
- a formatter with one layout that checks it keeps every token, refuses a program whose brackets do not match, and is tested stable and meaning-preserving on a corpus in every part of the language.
- **The command line:** `wai-quantum lsp` serves it over standard input and output (UTF-16 or UTF-8 positions, as agreed), with Run, Plan, Critique, Shortcuts and Receipt as lenses run in-process and signed with the user's key; `wai-quantum fmt` formats, `--check`s and `--write`s.
- **A tree-sitter grammar** (`wai/tools/tree-sitter-openqasm`): all of OpenQASM 3.1 and OpenQASM 2's forms, its generated parser committed, queries for highlighting, scopes and folds; every program in the repository's conformance material and examples parses with no error.
- **The editor extension** (`wai/tools/wai-quantum-editor`): highlighting, the server started from a configurable path with install commands when it is missing, the commands' answers in an output panel, a receipt saved as JSON; packaged as a `.vsix`, not published. Configurations for editors configured in Lua, by `languages.toml`, and with WebAssembly extensions (built for `wasm32-wasip2`).
- **CI:** `wai-quantum-editors.yml` runs the service's and the server's tests on Linux and macOS, regenerates the grammar and holds it to the committed parser, parses and formats every program in the repository (formatting twice changes nothing, and the engine draws the same circuit), and packages and checks the extension.
### wai-quantum: bring your own quantum
People bring their own quantum hardware, provider accounts, on-premises devices or simulators; WAI gives the engine, the free job service and the verifier, and never pays for or resells hardware time. A run on a person's provider is planned, its cost shown before it is submitted, checked, and sealed like any other job.
- **The open provider protocol** (`extensions/quantum-providers.md`, `wai.quantum.provider/1`): JSON messages over HTTP or to a local process on its standard input and output; `describe` (native gates, connectivity, qubits, durations, a calibration snapshot id with optional per-qubit and per-pair errors, the billing unit and price or free), `estimate`, `submit`, `status`, `result` (counts keyed by classical bits, the highest first) and `cancel`; a program in the device's own gates, every angle an exact fraction of a turn, with its OpenQASM 3 beside it; error codes; what the receipt proves and what it does not.
- **wai-quantum-qpu:**
- `open_provider`: the client over either transport (a credential only over https or to this machine; in a process's environment, never its command line), and `OpenProvider`, a `Provider` over it;
- `reference`: the reference provider, the protocol around the exact simulator, served both ways (`wai-qpu reference`); free or priced per shot or job in exact decimals, with status polls, a readout error and a credential checked by its hash; the template to copy;
- `conformance`: the checker and its vectors (`conformance/provider-v1.json`), the reference's counts pinned; jobs run only when every one is free or the person allows the spend;
- `registry`: the person's list (`~/.config/wai/quantum-providers.json`), holding no credential; credentials in the keychain (the system `security` tool, or `secret-tool`), else a 0600 file, labelled so in every listing; environment variables still work;
- `job`: the dry run (the exact native compile, the check by simulation, the provider's estimate and billing unit), the refusal of a run that is not free (or of unknown cost) until it is confirmed, and the seal of its counts;
- `Provider` gains `details`, `estimate`, `submit_seeded` and `cancel`, each with a default.
- **The engine** (`engine::provider`): what a provider job may ask (counts of a program measured at the end, every angle dyadic), the measured result labelled `measured` with the circuit's exact distribution beside it (total variation distance, Hellinger fidelity, and what the shots alone leave), and its seal, which `job::verify` checks. `catalog::read` refuses a job that names a provider, so the job service never runs one on its own methods.
- **The command line:** `providers add|list|remove|describe|test|free|reference`, and `run --provider NAME [--dry-run] [--yes]` (a run that is not free exits 3 without `--yes`). The stdio MCP server gains `providers_list`, `provider_estimate` and `run_on_provider`; the last says it can spend the person's money and refuses without `confirm_cost: true` when the run is not free.
- **The desktop app:** `providers_list`, `provider_estimate` and `run_on_provider` commands; the Studio's **Your hardware** switch lists the providers, asks for the estimate and shows a dialog before a run that is not free. In a browser tab the switch says such runs go through the desktop app or the command line, since a tab must not hold a credential.
- **The job service** never routes to a provider and never takes a credential (its README says so; a job naming one is refused).
- **Docs:** `wai-quantum-qpu/bring-your-own-quantum.md` (adding a provider, the protocol, the reference as a template, what the receipt proves and what it does not, and what a person does to connect a real device).
- **CI:** `wai-quantum-qpu.yml` pins its three test binaries and the new tests by name, with a macOS leg for the keychain in a keychain file the test makes; `wai-quantum-cli.yml` follows the crate and pins the five end-to-end tests; `wai-quantum-studio.yml` runs the hardware switch's logic test. Nothing in them spends money.
### wai-quantum: Python and notebooks, in-process
WAI Quantum from Python and notebooks, with the same answers and receipts as every other surface: the shared job engine in the process, a client for the job service, and notebook widgets.
- **`wai/wai-quantum-py`** (Python module `wai_quantum`): a pyo3 extension module over the shared job engine, built by maturin into one abi3 wheel per platform, Python 3.9 on.
- The engine's functions: `run`, `plan`, `lint`, `draw` (text and SVG), `layout`, `critique`, `improve`, `shortcuts`, `equivalent`, `verify`, `seal`, `canonical`, `digest`, `methods`, `accuracy_classes` and `history`, and `bloch`: each qubit's reduced state from the engine's expectation values of X, Y and Z.
- Answers are plain dicts and lists. A job is the service's form, with helpers: `counts`, `probabilities()` (the sampled frequencies), `expectations`, `amplitudes`, `method`, `accuracy` and `receipt`.
- Values cross one to one with JSON's: NaN, infinities, integers past 64 bits and keys that are not strings are refused, never guessed at.
- A refusal raises `EngineError` with the engine's message and the line it names; its `code` is the reader's stable diagnostic code (`Q103`) when the refusal is the reader's.
- **Keys:** a result is sealed with the key the caller passes (`Key`), by default this user's key, which the command line also signs with. The demo key and the key files are the command line's, so the same key signs on both.
- **`Service`:** a client in the standard library alone for the job service (`submit`, `status`, `result`, `cancel`, `run`). It names itself `wai-quantum-python/<version>` in each request, and checks each receipt in the process, offline.
- **Widgets** (the `widgets` extra): `Circuit`, `Result`, `Receipt` and `Bloch`, each a plain ES module. Each draws the engine's answer and computes nothing of its own, and each has a static HTML fallback (`Circuit` also SVG) for a notebook without the widget extension.
- **`engine::svg`:** a program's circuit as a standalone SVG drawing, with the Studio's geometry and classes, the same bytes on every platform. Each gate can be marked with whose hand it is in.
- **Docs as code:** five notebooks (getting started, a Bell pair and its receipt, equivalence, a noisy run, a job on a local service), run in CI and held to their stored outputs.
- **Tests:** 62 pytest tests hold the package to the other surfaces:
- a job run in Python equals the command line's and the service's, except for each host's time and its word on energy;
- the plans, lints, critiques, drawings, method catalogue, equivalence verdicts and improvements are the command line's;
- receipts verify both ways, and the digest recomputed from Python's canonical bytes is the command line's;
- the 3,000-odd ECMAScript number vectors reach the engine bit for bit;
- the widgets' front ends run under node on the payloads the package builds.
- **CI:**
- `wai-quantum-py.yml` runs `ci/check.sh` on `linux` and `macos`: the wheel, pytest with every test required, the notebooks, and a smoke check on Python 3.9 with the wheel alone.
- `wai-quantum-py-release.yml` builds the wheels on a `wai-quantum-cli-v*` tag: macOS arm64 and x86_64, Linux x86_64 and arm64 (manylinux2014, linked with zig) and Windows x86_64. It attaches them to the forge release with their SHA-256 and publishes nothing to a package index.
### wai-quantum: the agent front door
- **`engine::guide`:** what an agent, or a person new to the engine, is told, from one place. It holds the language the reader takes; the reader's diagnostics, each refusal with what to write instead, every entry held to the reader by a test; the methods and their accuracy classes; and worked examples, each with its intent (an exact distribution, or a reference program to equal or whose state to prepare). It also holds the rules an agent keeps: every number from a tool, the intent checked before success, this machine first, cost and energy stated before any hardware run, no secrets, fetched pages as data, versions from the installed command line, and taste choices only the person's.
- **MCP resources and prompts on both servers.** The command line's (`wai-quantum mcp`) and the job service's (`POST /mcp`) serve `resources/list`, `resources/read`, `prompts/list` and `prompts/get` from `engine::guide`. The resources are the language, the diagnostics, the methods and the examples. The prompts are writing and verifying a circuit for an intent, explaining a result and its receipt, and repairing a program from its diagnostics, each naming its own server's tools. Both servers' instructions carry the rules. Each server has a `server.json` in the MCP registry's format; neither has been submitted.
- **`wai-quantum new DIR --template bell|ghz|grover|noisy-bell`:** a project with the program, `intent.json`, `test.sh`, a CI script and an example workflow, `mcp.json` and `AGENTS.md`. Its test passes as made.
- **`wai-quantum test`:** each intent checked by the engine. A distribution is computed exactly by the state vector, or under noise by the density matrix, and compared by total variation distance. A reference program goes through the equivalence ladder, and a state is compared from |0…0⟩. The run's receipt and the check's are saved, both signed, the check's binding the run's. It exits 0, 1 or 2 for holds, fails or unchecked.
- **`wai-quantum setup-agent [DIR]`:** the block between `<!-- wai-quantum:start -->` and `<!-- wai-quantum:end -->` in `AGENTS.md`, written, refreshed, or checked with `--check`, never touching text outside the markers.
- **`wai-quantum mcp config` and `wai-quantum mcp install --file PATH`:** the local server's entry in the common `mcpServers` form, printed or merged into a configuration file the person names. Other entries are kept, and a different entry is refused without `--force`.
- **`wai-quantum docs`:** the resources offline, the `AGENTS.md` block, `llms.txt` and `llms-full.txt`, and the Agent Skill (`skills/wai-quantum/`). The generated files are committed in the command line's tree and held by tests to both servers' live tool lists.
### wai-quantum: a study of the co-design, every measure the engine's
The measurement harness the co-design report puts under every feature: each feature behind a flag, compared against a control, in a study whose protocol is sealed before any data.
- **The protocol** (`study::Protocol`, `seal_protocol`):
- the study, its hypotheses, the primary measure, the conditions (each a setting of the Studio's features: `study::FLAGS`), the control, and the analysis (resamples, seed, level);
- checked, then sealed with the researcher's key before any data, its digest naming the study.
- **Assignment** (`study::assign`): the digest and the participant's key hashed together, so the condition is fixed before they begin and anyone can check it.
- **A record** (`seal_record`): what one participant made and did, sealed with their key: the program's history, their predictions, their choices among candidates (with the engine's best of them), their probe tasks done without the assistant, and their answers on ownership.
- **The measures** (`study::measure`), the report's eight, each computed again by the engine from the record:
- quality: the person's edits the engine proves equal to what they replaced, and the share that made it cheaper, with its trend;
- diversity (Vendi) and novelty of their designs;
- selection skill against chance;
- calibration: the mean distance of a prediction from the run, and the slope;
- unassisted skill: four probe tasks scored by a workbench specification each, a score of 1 matching the reference;
- ownership: five items from 1 to 7, beside the authorship share by the history;
- convergence on a model's proposals.
- **The cohort report** (`study::cohort`):
- every record checked: the protocol's digest, its seal, the key that sealed it, the condition its key is assigned, and one record per participant; a record that fails is refused with the reason;
- per condition, the mean of each measure;
- each condition's difference from the control on the primary measure, with a seeded bootstrap interval;
- Cronbach's alpha of the ownership items, and the Vendi score of each condition's final designs.
- **The command line:** `wai-quantum study catalog | new | assign | measure | cohort` (`--out` seals the report).
- **For agents:** `study_measure` and `study_cohort`. The probes are not offered to agents: a probe is done without the assistant.
- **The Studio:**
- the Study pane: Features, each one switchable off, and held by a study's condition;
- "Load a protocol", which shows what the study is about before "Take part";
- a record kept only once asked: the choices in Alternatives and the Workbench, "How much is this yours?", and probe tasks with the assistant paused;
- "My record", and "Export my record", sealed with the browser's key.
- **For hosts:** `study::call` and `call_sealing`; `QuantumEngine::study` in the page, which seals with the page's key, and `study` in the desktop app.
- **CI:** wai-web's native engine tests count 14.
### wai-quantum 0.6.16 and wai-quantum-cli 0.1.4: the person's rules, what is new, taste, rewrites and convergence
This release carries the five entries below it to the package registry, and 0.6.15's with them: 0.6.15 and 0.1.3 went out as binaries and to the package taps only. `wai-quantum-cli` 0.1.4 requires 0.6.16.
- **wai-quantum 0.6.16:**
- the person's rules for proposals (`engine::locks`), with the blocked changes kept in a version's review;
- what is new in a program, the known constructions found again, and aberrations (`engine::novelty`, `discover::novelty`);
- a person's taste, learned from their own choices between programs proved equal (`engine::taste`);
- rewrites, each checked on the qubits they touch, and equivalence proved by them at any width (`engine::rewrite`; `equiv::equivalent_programs` answers `how: rewrites`);
- whether designs converge on a model's, and a cohort's diversity (`engine::converge`).
- **wai-quantum-cli 0.1.4:**
- `locks check | brief`, `novelty`, `discover novelty`, `taste choose | show | rank | next`, `rewrite options | apply | simplify | explain` and `converge history | cohort`;
- the MCP tools `novelty_qasm`, `discover_novelty`, `rewrite_qasm`, `converge_cohort`, `converge_history`, `taste_rank` and `taste_next`, and `history_commit` refusing an edit that breaks the person's rules.
- The desktop app requires 0.6.16 too. Every lockfile that holds wai-quantum or wai-quantum-cli moves with them, so a `--locked` build still reads its own.
### wai-quantum: whether designs converge on a model's, and a while on one's own
Feature 12 of the co-design report: a convergence monitor.
- **Along a history** (`converge::history`): for each version the person made after a model proposed, its structural distance from the proposals in its line of work (the whole proposal, taken or not) and the model's share of its gates (alone or then edited by the person). It is converging when most of the program is the model's, or when the last versions come closer to the proposals each time; `why` says which.
- **The assistant-off probes:** versions noted `organic exploration` are set beside the rest, by their distance from the proposals.
- **A cohort** (`converge::cohort`): the Vendi score over `1 − distance` (Jacobi eigenvalues), the effective number of distinct designs; each program's distance to the centroid of their gate-token bags; the medoid and the one built least like the rest; with times, the score in windows and whether it narrows.
- **The command line:** `wai-quantum converge history | cohort`.
- **For agents:** `converge_cohort` and `converge_history`.
- **The Studio:** "Your own way" under Versions, and "Explore on my own for a while". While it is on, the assistant only asks and offers no program, Explore and "Ask the model" wait, and the person's versions are noted; "End" lets the model propose again.
- **For hosts:** `converge::call`, `engine_converge` in the page, `converge` in the desktop app.
- **CI:** wai-web's native engine tests count 13.
### wai-quantum: rewrites, each checked, and equivalence proved by them at any width
Feature 11 of the co-design report: a verified-rewrite lens shared by the person and the assistant, and the general equivalence check it needs first.
- **The rewrites** (`engine::rewrite`): a gate and its inverse cancel; two rotations about one axis merge, and the phase gates add up (`t t` is `s`); a gate between two Hadamards becomes its conjugate (`h x h` is `z`, `h cx h` on the target is `cz`); a gate that does nothing is dropped; a gate is carried past gates it commutes with to cancel or merge with its partner; two gates that commute trade places. The rules propose and the engine decides: each rewrite's gates are checked against its replacement on just the qubits they touch, by tableau or on every basis state (`equiv::proved_equal`), so no rule is trusted.
- **Equivalence proved by rewriting** (`rewrite::prove`): one program followed by the other's inverse is rewritten toward nothing. When nothing is left, the two are the same unitary up to a global phase at any width and with any gates. `equiv::equivalent_programs` now tries it after the exact check and before random inputs, answering `how: rewrites`, which the workbench and the taste model count as a proof. When it does not reduce, nothing is concluded and the next check runs.
- **A chain** (`rewrite::explain`): both programs simplified to a fixed point and compared, gate for gate or, where they differ only in the order of gates that commute, by a proof. Each step is in words.
- **The lens** (`rewrite::options`): every rewrite a program admits as an edit to its own lines (a merge writes `rz(0.25 + 0.5)`, comments stay), read back and compared gate for gate before it is offered. A generator is rewritten as the circuit it writes out.
- **The command line:** `wai-quantum rewrite options | apply | simplify | explain`.
- **For agents:** `rewrite_qasm`.
- **The Studio:** "Rewrites" under the circuit (each rewrite in words, its gates marked on the diagram, "Take" to offer it as the engine's proposal, the list following the program); Alternatives says how the draft becomes a candidate that is the same circuit, as a chain; an equivalence proved by rewriting reads "by checked rewrites".
- **For hosts:** `rewrite::call`, `engine_rewrite` in the page, `rewrite` in the desktop app.
- **CI:** wai-web's native engine tests count 12.
### wai-quantum: a person's taste, learned from their own choices between equals
Feature 10 of the co-design report: a personal, plural taste model.
- **The model** (`engine::taste`):
- a choice is recorded only when the engine proves the two programs the same circuit (`equiv::equivalent_programs`) or the same state (`equiv::same_state`), by a proof and never by random inputs alone: it is about taste, never correctness;
- a Bradley–Terry logistic over differences of features the engine measures (gates, two-qubit gates, depth, two-qubit depth, T gates, modelled error) and of the program's text (statements, kinds of gate, repetition, comments), fitted with a ridge penalty by Newton's method, so the same choices give the same weights on every host; a feature that barely varied over the choices is scaled by a floor on its spread, so it cannot turn a large difference between candidates into certainty;
- the rubric's parts (economy, clarity, insight, precision, utility): each learned from the choices that name it, and a ranking says where a part, or the cost, puts another first;
- profiles are personal and plural, by name, and never pooled;
- typicality (one less the novelty of `engine::novelty`) is shown beside each ranking and never learned; the novelty dial (−1 to 1) blends the order by taste with the order by novelty, each standardised over the candidates, so at either end the order is by novelty alone;
- measured: how many of its own choices a profile predicts, each from the ones before it and each held out from the rest (over the last 100), the choices nothing measured separates, and whether a ranking's first three are built more alike than the first three by cost;
- `next`: the pair the profile is least sure of that the engine proves equal.
- **The command line:** `wai-quantum taste choose | show | rank | next`, over files or a specification's elites (`--spec`), the profiles in `quantum-taste.json` beside the user's key.
- **For agents:** `taste_rank` and `taste_next` read the person's profile. No tool records a choice: only the person chooses.
- **The Studio:** "Your taste" in the Workbench (Compare two, with the part a choice was about; Rank by my taste, with each one's typicality and the dial; profiles by name). An alternative that is the same circuit as the draft, taken whole or refused whole, is a choice, and Improve orders its candidates by the profile.
- **For hosts:** `taste::call`, `engine_taste` in the page and `taste` in the desktop app.
- **CI:** wai-web's native engine tests count 11.
### wai-quantum: what is new, what is a known construction found again, and aberrations
Feature 9 of the co-design report: a novelty, rediscovery and serendipity ledger.
- **What is new** (`engine::novelty`):
- the inspiring set: GHZ by a chain of CNOTs, by doubling and by fan-out, the uniform superposition and the toolchain's quantum Fourier transform, each built at the program's width (to 12 qubits), and whatever programs the caller adds;
- a structural distance, 0 to 1: the weighted Jaccard distance between two programs' multisets of gate tokens (a gate's kind and its qubits relative to its first) and of consecutive pairs of them;
- rediscovery: a program the engine proves does what a known construction does (`equiv::same_state` for a preparation, `equiv::equivalent_programs` otherwise; never on random inputs alone) is that construction found again. Built unlike it (a distance of 0.5 or more) it is a different route to a known result. A program that is itself one known construction is not a different route to another;
- novelty, the distance to the nearest of the set, named;
- with a reference, the measures a program beats it on that the cost does not weigh.
- **Aberrations** (Wiggins's term) in the workbench: a candidate that breaks only the specification's rules (its gates, its connectivity) is judged again without them. If it is verified there, its evaluation carries the rule it broke (`broke`: the gate, or the line), its reason, and the cost, axes and strength it would have. It is never kept.
- `discover::novelty` reads one archive entry at the specification's largest size against the archive's other elites.
- **The command line:** `wai-quantum novelty` (`--against`, `--reference`, `--weigh`, `--input`) and `discover novelty`; `discover eval` and `discover submit` say an aberration's rule and the cost it would have.
- **For agents:** `novelty_qasm` and `discover_novelty`.
- **The Studio:**
- each Alternatives card says what is new in it, against the known constructions and the draft, and marks a different route to a known result;
- the Workbench names each kept generator for what it is, shows it on the entry with what it beats the reference on unasked, and logs an aberration with "Try without that rule", which loosens the specification for a new archive;
- Found keeps what is worth a second look (a different route, an aberration, a verified entry far from everything compared) to open, save with a note, or dismiss, and counts what was flagged and saved;
- the assistant has the tool, and may call a program new only when the engine says so.
- **For hosts:** `novelty::call`, `engine_novelty` and `engine_discover_novelty` in the page, `novelty` and `discover_novelty` in the desktop app, which now builds the toolchain's QFT into its inspiring set.
- **CI:** wai-web's native engine tests count 10.
### wai-quantum: the person's rules for proposals, checked change by change
Feature 7 of the co-design report: region-scoped edits with locks and keep/avoid marks.
- **The rules** (`engine::locks`):
- held blocks, marked in the program between `// @lock` and `// @unlock`, so they travel with it to a model, an agent and a file;
- the gates a change may add; the qubit declarations held;
- gates and text to avoid; and the region a rewrite must keep to.
- **The check** (`locks::check`) names each change a proposal makes (`versions::changes`) and the rules it breaks.
- Only what a change adds is judged, so a gate the program already used is never a proposal's breach.
- A generator's gates are read at its inputs, and a proposal that does not read cannot pass a gate rule.
- **The brief** (`locks::brief`) states the rules as a model is told them.
- **On a version:** `versions::Review` keeps the changes the rules blocked (`History::propose_with`), and a change both blocked and accepted is refused there and in `History::verify`. A review with nothing blocked has the digest it had before.
- **The command line:**
- `wai-quantum locks check | brief`;
- `history changes` names each change's breaches;
- `history propose` refuses a blocked change, and `--accept all` takes the allowed ones;
- the rules sit beside the program as `prog.locks.json`.
- **For agents:** `history_commit` refuses an agent's edit that breaks a rule, with the rule, and `history_changes` shows the breaches and the rules.
- **The Studio's Rules:**
- "Hold the selected lines" marks them, and they are shaded in the editor, their gates marked on the diagram;
- the gates allowed, the qubit declarations, what to avoid;
- "Keep the next proposal to the selected lines".
- In the review, a change that breaks a rule is shown with it and cannot be taken, and the version records it as blocked.
- Alternatives leaves out a candidate whose every change breaks a rule, and the model is told the rules with every request.
- **For hosts:** `locks::call`, `engine_locks` in the page and `locks` in the desktop app; `locks::held_ops` gives the held gates.
- `versions::call` and `locks::check` name an unreadable program `unread`, not `error`: a page's worker reads an answer with an `error` as a failed call, so an unreadable draft had lost its lines' authors.
### wai-quantum 0.6.15 and wai-quantum-cli 0.1.3: generators, the specification-and-evaluator workbench, and a program's history, attributed
This release carries the four entries below it to the package registry, and `wai-quantum-cli`
0.1.3 requires it.
- **wai-quantum 0.6.15:**
- OpenQASM 3 generators (`qasm::parse_with`, `qasm::inputs`, `qasm::instantiate`);
- the workbench (`engine::discover`, and `equiv::same_state` for the `prepares` task);
- the energy reader moved into the engine (`engine::energy`);
- a generator linted and critiqued at its inputs (`job::lint_with`, `critique::critique_with`);
- a program's history, attributed (`engine::versions`).
- **wai-quantum-cli 0.1.3:**
- `--input NAME=VALUE` on every command, and `instance`;
- `discover new | search | eval | submit | show | verify`, with two specifications in `discover/`;
- `history start | commit | changes | propose | merge | log | blame | verify`, and `--history` on `run`, `submit` and `verify`;
- the MCP tools `discover_evaluate`, `discover_submit`, `discover_archive`, `history_commit`, `history_changes`, `history_blame` and `history_log`.
### wai-quantum: a program's history, attributed, and the Studio's Versions
- **The history** (`engine::versions`): every version of a program, in a chain.
- Each version names its parents: none for the first, one for a change, two for a merge.
- Each names who made it: the person, the assistant (a model), an outside agent, the engine, or where the program came from.
- Each names how it was made: an edit, a proposal reviewed change by change, or a merge.
- A version's digest covers its parents' digests, its source, who made it and how, and the recording host's key seals it. `History::verify` checks every digest and seal, and that each review gives its version's source.
- **A proposal reviewed change by change** (`changes`, `apply`, `History::propose`):
- the changes are a line-level diff, a longest common subsequence by Myers' algorithm;
- only the changes accepted reach the program;
- the version keeps the whole proposal and which changes were taken, so the rejected ones stay readable.
- **Whose hand** (`History::blame`, `authorship`, `gate_hands`):
- a line keeps its author until someone changes it;
- a model's or an agent's line that the person then edits is in both hands (`assistant_then_person`, `agent_then_person`);
- an edit back to an earlier program (an undo) keeps that program's authors, and a merge keeps each line's author from either side;
- the gates follow their lines, so a generator's are counted at given inputs, each by the line in the loop that makes it.
- **On the receipt:** `History::attest` gives a version's digest and the gates and lines in each hand. A job's request carries it as `history`, so the receipt binds it and a changed count fails the signature. `History::check_job` recomputes the account from the history, and checks that the job ran that version's program.
- **The command line:**
- `wai-quantum history start | commit | changes | propose | merge | log | blame | verify`, with the history in a file beside the program;
- `run --history` and `submit --history`;
- `verify --history`.
- **For agents:** `wai-quantum mcp` adds `history_commit`, `history_changes`, `history_blame` and `history_log`. An agent's edit is recorded as an outside agent's, under the name its client gave in `initialize`, whatever it says of itself.
- **The Studio's Versions pane:**
- **Recording:** what the reader types is recorded as theirs once they pause. Every proposal (the assistant's program, an alternative, a generator from the Workbench) is offered change by change, and the editor waits until the reader chooses.
- **Lines of work:** drawn in lanes. An older version opened and edited starts a line of its own, and "Bring in" merges another line change by change.
- **"Who wrote it"** marks each line in the gutter and each gate on the diagram.
- **On the receipt:** a run's receipt binds the account, and the result shows it, recomputed here. The versions are kept in the browser and download as `program.qasm` and `program.history.json` for the command line.
- **Hosts:** `window.quantumStudio.propose` lets an embedding page offer a program as someone's proposal. The page and the desktop app call the engine through one `history` binding.
- **Fixes in the Studio:**
- a run is shown as running before the history's account is made, so a quick check after it does not read the last result;
- the draft is kept as the page goes, not only after a redraw's pause.
### Quantum Studio: the Workbench, and generators in the editor
- **Generators in the editor.** A program with integer inputs shows a field for each under the
editor. The lint and the critique read the generator at those values, keeping its own lines
(`job::lint_with`, `critique::critique_with`; a smell in a generator offers no Fix). The diagram,
the plan, a run here or on the service, the lens and the alternatives take the circuit it writes
out (`qasm::instantiate`, in the page and in the desktop app).
- **The Workbench pane** puts `engine::discover` in the Studio. The program in the editor is the
reference. A form gives the specification: the task, the sizes, the gates, the connectivity, the
cost's weights and the map's axes. The archive's elites are plotted on the two axes, the
cheapest marked. An entry shows its cost, how it was checked, its seal and its lineage, and opens
in the editor.
- **Three proposers, each named on what it found:** the reader's program, the built-in blind
search (in steps of 25, so its progress shows), and the model. The model is a proposer
(`Assistant.propose`): it is told the specification and shown the cheapest generators found,
with the costs the engine measured. The engine judges what it writes, as it judges everything
else.
- **Sealed where it runs:** in a tab with the browser's key, in the desktop app with the app's,
which also counts a search's seconds and joules. The archive is kept in the browser and comes
back with the page. It downloads beside its specification, as `wai-quantum discover verify` reads
them.
- **The assistant's brief** no longer says loops are refused: `while`, `inv` and `pow` are, and
a generator is read at the inputs bar's values.
- The Studio's browser test adds:
- a generator linted, drawn and run at its value;
- the Workbench started from it, with a hundred blind edits judged;
- the reader's log-depth tree kept as the cheapest, opened in the editor, and back after a reload;
- a scripted model's generators judged at every size: one kept, while a wrong one and one using
a gate the specification forbids are left out with their reasons.
### wai-quantum: the specification-and-evaluator workbench
- **A specification** (`engine::discover::Spec`, a JSON file for the command line):
- the task: the same unitary as a reference generator (`equivalent`), or the same state from
|0…0⟩ (`prepares`, a new check, `equiv::same_state`);
- the sizes to check at;
- the gates allowed, and the connectivity (`all`, or neighbours on a `line`);
- the cost: weights on the critique's measures, summed over the sizes;
- the archive's two axes, and a budget in evaluations, seconds and joules.
- **The evaluator is the engine.** A candidate is verified only when it reads, keeps the rules
and passes the check at every size; the first size that fails decides, and the larger ones are
not run. The weakest check it passed is named: `tableau`, `exact` and `state` decide it;
`random` is a check with no false "different".
- **The archive** keeps the cheapest verified generator in every cell of the two axes
(MAP-Elites). Each entry has its parent, its proposer and a note. A generator seen before is
not evaluated again.
- The reference is the first entry, so an improvement means cheaper than what the person
brought.
- An archive is bound to its specification's digest, the budget left out.
- Each entry kept is sealed with the searcher's key, over the specification's digest, the
source, the proposer and every size's verdict and measures. `Archive::verify` checks every
seal, and that it binds its entry.
- **Proposers stand outside it.** The built-in one (`mutate`, `search`) makes blind, seeded,
statement-level edits: a baseline that replays from its seed. A person or an agent submits
through the same `Archive::submit`.
- **The command line:** `wai-quantum discover new | search | eval | submit | show | verify`.
A specification's reference may be `@file.qasm` beside it. `search` reports evaluations,
seconds and, on macOS, the joules its own process spent. Two specifications ship:
`discover/ghz-line.json` and `discover/ghz-all.json`.
- **For agents:** `wai-quantum mcp` adds `discover_evaluate`, `discover_submit` and
`discover_archive`, so an outside agent proposes into the same archive and is named as the
proposer. Every tool that takes a program also takes a generator, with `inputs`.
- **One energy reader** (`engine::energy`), moved from the job service: the figure a worker's
result carries, unchanged, and a running process's own count (`energy::own`, a lower bound by
its threads still on a core). The service keeps only its wait-without-reaping.
- Measured on the build host, `ghz-all.json`: 1,500 blind edits in 4.6 s and 24 J. 139 were
verified and 29 kept, and none beat the reference. The doubling tree costs 25 against the
reference's 49. Every one of the 29 seals held.
### wai-quantum: OpenQASM 3 generators — one program for a family of circuits
- **The reader takes generators** (`qasm::parse_with`, `qasm::inputs`). `input int n;` and `input
uint n;` take an integer given when the program is read. `const int k = …;` names one. `for i
in [a:b] { … }` (also `[a:step:b]`, both ends included as OpenQASM 3 reads a range, and `{a, b,
c}`, nested) is unrolled once per value. Indices and sizes take integer arithmetic (`q[2 * i +
1]`, `qubit[n + 1] q;`): `+ - * / %` with C's truncating division, `**`, comparisons, `&& ||
!`. An `if` whose condition reads only integers is decided on reading; one that reads bits
stays classical control. A gate a loop makes keeps the line of the statement in the loop's
body, so a critique still points into the generator.
- **An instance written out** (`qasm::instantiate`): the circuit at given inputs as plain
OpenQASM, with no `input`, `const` or loop left, for any surface that takes a program as
text. Reading it gives the same program, gate for gate.
- **Bounded.** A program unrolls to at most 20 million tokens, every integer operation is
checked for overflow, and a name an integer would shadow (a register, a gate's parameter or
qubit, a word of the language) is refused with its line. A loop variable may still share a
gate's name, such as the identity `i`. `while`, `inv` and `pow` are still refused.
- **The command line** takes `--input NAME=VALUE` on every command, so `run`, `plan`,
`critique`, `equivalent`, `shortcuts` and the rest each read one circuit of the family.
`wai-quantum instance gen.qasm --input n=5` prints that circuit.
- A program with no `input`, `const` or `for` reads exactly as before.
### wai-quantum-service: sandboxed workers, and limits no client can multiply
- **Workers run sandboxed** (macOS). Each job's worker runs under `sandbox-exec`: no network,
no file writes, and no program but itself. It reads its job on stdin and writes its result on
stdout, so it needs none of them. A flaw in parsing a submitted circuit then reaches neither
the host's files nor the network nor a shell. The pipes it was given stay writable, and its
energy is read as before. `WAI_QSVC_SANDBOX=0` turns it off for debugging.
- **An IPv6 client is its /64.** Limits were counted per address, and a subscriber is usually
given a whole /64, so rotating through it handed out fresh limits with every request. A
client is now an IPv4 address or an IPv6 /64. An IPv4 address carried in IPv6 counts as
itself.
- **One client holds at most one job's worth of memory.** Three active jobs from one client
could hold the whole 96 GiB budget. A client's queued and running jobs may now hold together
at most `WAI_QSVC_CLIENT_GB`, by default the size one job may take. Any client can still run
its largest job, and no client holds the rest.
- `GET /v1` and the MCP server publish `memory_per_client_bytes`. `GET /v1` also publishes how
a client is counted and whether workers are sandboxed.
### wai-quantum 0.6.14 and wai-quantum-cli 0.1.2: contraction trees that sweep along the line, and the design checks on the job service
0.6.13 was not published on its own: this release carries it to the package registry, and `wai-quantum-cli` 0.1.2 requires it.
- **Sweep seeds for the contraction search** (`quantum_tn`):
- `Network::amplitude_split_placed` builds the same network as `amplitude_split`, index for index, and gives each index its place in the circuit: a qubit and a time.
- `tensor_places` gives each tensor its place.
- `sweep_orders` orders the tensors qubit by qubit along time, or time by time across the qubits.
- `sweep_path` absorbs them in that order.
- `find_path_seeded` and `find_path_restarts_seeded` score such trees beside the trials, and polish the best of them all alike. A seed that is not a tree of the network is ignored.
A deep circuit on a line, swept qubit by qubit, keeps only a time slice of the bonds between two neighbouring qubits as its boundary: about half its depth for a controlled-Z brickwork. The bipartition and greedy families rarely find that tree on their own. The engine's `tensor_network` method and the thorough classical-shortcut lens now take both sweeps as seeds.
- **On the 70-qubit random graph-state circuit** (CZ-depth 70), for one amplitude:
| | largest intermediate | multiply-adds |
|---|---|---|
| the search alone, 16 trials | 2^59 | 10^23.5 |
| the search with the sweeps as seeds, 16 trials | 2^36 | 10^14.6 |
| the thorough lens, before | 2^48 | 2^56.1 |
| the thorough lens, with the sweeps | 2^36 | 2^48.7 |
The thorough check took 182 s, down from 238 s. The plans pinned in `tests/bit_identity.rs` call `find_path` itself and do not change.
- **The design checks on the job service:**
- `POST /v1/critique`;
- `POST /v1/equivalent`;
- `POST /v1/improve`;
- `POST /v1/shortcuts`;
- the matching MCP tools: `critique_qasm`, `check_equivalent`, `improve_qasm`, `classical_shortcuts`.
They answer at once, in the service's own process, within bounds:
- each counts against the client's submissions, as a job does;
- at most two run at a time, and one that waits ten seconds for a turn is refused (503);
- some checks are refused (422) with the command that runs them locally: an equivalence check past 4·10⁹ gate amplitudes, an improvement whose checks would pass that, and a thorough search for shortcuts.
The service's smoke checks cover each one.
### wai-quantum 0.6.13 and wai-quantum-cli 0.1.1: the engine critiques a design, checks two circuits are the same, offers alternatives, and prices its classical shortcuts
The engine is the microscope and the judge, a local model is a muse that waits to be asked, and the reader is the author. Every figure below is computed by the engine. Nothing changes the reader's program until they apply a change.
- **`engine::critique`** gives a program's measures and its smells before it runs.
- The measures: gates by kind; one-, two- and three-qubit gates; T gates; gates off the Clifford angles; depth and two-qubit depth; and the measured qubits.
- It says whether the program is a Clifford circuit.
- It gives a fidelity at reference error rates (`REFERENCE_RATES`), named as a model of a device and not a run.
- The smells, each with its lines and its operations: gates that cancel; rotations that merge; rotations that do nothing; qubits nothing uses; and qubits acted on but never measured.
- In a program that measures in the middle, a measurement, a reset or a condition is a barrier, and no smell spans one.
- **`critique::variants`** rewrites the program in its own text with its smells fixed.
- It offers every fix, to a fixed point, since one fix can bring two gates together. It also offers each first-round fix alone, and each distinct program once.
- A fix touches only a statement alone on its line. A merge keeps the program's own expressions, as `rz(pi/4 + pi/8)`, and its comment.
- Each candidate carries its measures and the equivalence check's verdict against the program. A candidate the check finds different is never offered.
- **`engine::equiv`** says whether two programs are the same circuit up to a global phase, and how it knows:
- Clifford circuits by their tableaus, at any number of qubits;
- otherwise on every basis state, up to 8 qubits and within a work budget;
- otherwise on three random product states, up to 22 qubits, which is evidence and not proof;
- otherwise unchecked, with the reason.
A difference names the input it was found on. Programs that measure in the middle are not compared. Programs of different widths, or that write different qubits to the bits, differ by shape.
- **`qasm::parse_lines`** gives each operation's source line: a broadcast's gates and a definition's expansion share the call's line. **`draw::layout`** gives each gate the index of its operation (`op`), so a diagram can mark the operations a smell names.
- **In the browser:** `wai-web` exports `engine_critique`, `engine_equivalent` and `engine_variants`, tested native and under wasm.
- **Quantum Studio, in the browser and on the desktop:**
- **The critique, always on.** Under the diagram it shows the measures, each with its change since the last run, a Clifford badge and the modelled fidelity. The smells are listed with their lines. Their gates are flagged on the diagram, and each gate's tooltip names its smell. Show selects the lines and marks the gates. Fix applies the engine's rewrite, and the note says how the result was checked the same circuit; Undo brings the lines back.
- **Alternatives.** Improve offers the same circuit, cheaper: the engine's rewrites, and the model's when one is connected, each checked against the draft. A rewrite the check finds different is listed as not offered, with the reason. Explore asks the model for different circuits toward an aim the reader names. The engine reads, measures and compares each one, and gives how many lines it changes. An atlas plots the draft and each alternative by two-qubit gates and depth. Apply puts one in the editor.
- **Predict first** (off by default). Before a run, the reader names the outcome they expect most often and its share of the shots. The run's counts are set against it, and a tally of how close their predictions come is kept on this device.
- **The assistant asks questions first, and speaks only when asked.** The reader chooses how it helps: it asks questions first (the default), suggests changes, or writes programs. They also choose when it speaks: only when asked (the default), or with a hint offered, never sent, after a problem the engine has reported for a while or two refused runs in a row. New quick asks are "Ask me a question" and "Hint". New tools are `critique_qasm` and `check_equivalent`. For Alternatives it is called once per request, without tools and outside the conversation, and only proposes.
- The desktop app answers `critique`, `equivalent` and `variants` natively.
- **`wai-quantum-cli` 0.1.1:**
- `wai-quantum critique` gives the measures and smells.
- `wai-quantum equivalent a.qasm b.qasm` exits 0 for the same circuit, 1 for different and 2 for unchecked.
- `wai-quantum improve` prints the improved program, and what changed and how it was checked on standard error.
- `wai-quantum shortcuts prog.qasm [--thorough] [--json]` prices each exact classical route.
- The MCP server adds `critique_qasm`, `check_equivalent`, `improve_qasm` and `classical_shortcuts`.
- **`engine::shortcuts`** lists every exact classical route the engine knows for simulating a program, with its cost on that program:
- **the stabilizer tableau**, for Clifford circuits, at any width;
- **the cut-rank sum**, for a Clifford circuit followed by one layer of one-qubit gates. Its cost is a table of 2^cut entries along the best order found, and includes running the bulk's tableau and reducing it to graph form;
- **an exact matrix product state** along the qubits' own order. Its bond is bounded across each cut by the product of the operator Schmidt ranks of the gates that cross it, each computed from the gate's matrix, and never by more than the smaller side's dimension. The bound is rigorous: a test holds it against the true Schmidt rank at every cut of random circuits of every two- and three-qubit gate;
- **the light cone** of a one-qubit expectation value;
- **a tensor-network contraction** of one amplitude of a seeded random output, by the engine's tree search (when asked to be thorough);
- **the state vector**.
Each route gives its memory, the log2 of its operations to a first answer, and a time at a reference rate of 10¹⁰ operations a second, which is a reference and not a measurement. The cheapest route is named for counts, amplitudes, expectation values and one-qubit expectation values. A summary leads with "classically easy at any width" for a Clifford circuit.
A cheap route proves a design classically easy. None being cheap proves nothing about hardness: other methods, approximate ones above all, may cost far less, and the result says so.
- **On the 70-qubit random graph-state circuit** (CZ-depth 70, a closing layer of face-basis rotations), the quick check finds the cut-rank sum in under 2 s on one workstation. Its widest cut is 30 bits along a quick order search, at about 2^35 operations a sample. The matrix product state's bound is 2^35 at the middle cut, and the light cone of every qubit is all 70.
- **The lens in the browser and on the desktop:** `wai-web` exports `engine_shortcuts`, tested native and in wasm. The desktop app answers `shortcuts` off the window's thread.
- **The lens in Quantum Studio:**
- Under the critique, "Classical cost" prices each route and names the cheapest. "Thorough" adds the contraction search.
- A check is marked stale once the program changes.
- The assistant gains the tool `classical_shortcuts`.
### wai-quantum 0.6.12: cheaper contraction trees, and the cut-rank table on the GPU
0.6.11 was not published on its own: this release carries it to the package registry, and `wai-quantum-cli` 0.1.0 requires it, since the command line's `metal` feature turns on `quantum_cutrank_metal`.
One pinned digest changes, by design: the tensor-network plan in `tests/bit_identity.rs`, because the search now finds a cheaper plan. The native and WASI builds compute the same new digest. Every other pin is unchanged.
- **Each bisection runs two V-cycles.** After the split is projected onto the full graph:
- the graph is coarsened again, matching nodes only within a side, so the split carries to the coarse graph with the same cut;
- the split is refined there and on the way back down, which never raises the cut.
- **The best few trees are annealed after reconfiguration** (`quantum_tn::anneal`):
- random rotations at each node are accepted on the change in log total cost, at a falling temperature;
- the cheapest tree seen is kept and reconfigured again;
- a rotation changes the cost of two contractions only, so each move costs two set sizes.
- **On the 61-qubit Nighthawk logical circuits** (split and reduced, 1024 trials, 8 restarts, the median of 4 seeds). The figures are log10 complex multiply-adds for one amplitude:
| depth | 0.6.11 | 0.6.12 |
|---|---|---|
| 8 | 5.30 | 5.29 |
| 12 | 8.10 | 8.09 |
| 16 | 11.12 | 10.97 |
| 20 | 14.02 | 13.68 |
The search takes about 1.7 times as long at depth 20.
- **`quantum_cutrank_metal`** (feature `quantum_cutrank_metal`, macOS) runs the cut-rank table on the platform's GPU:
- one shared buffer of `2^cut` single-precision entries, at most `2³¹`;
- one dispatch per pass of the walk, in its order and with its arithmetic, done in `f32`, so an amplitude is one command buffer;
- a sample's sums reduced per threadgroup on the device and added on the host in `f64`, in order, with the outcome drawn as the host's walk draws it;
- a batch of shots sampled side by side, each shot's table at its own offset and its weights in a small per-shot array, so a qubit is one command buffer for the whole batch;
- each shot drawing from a stream of its own, so a seed's samples are the same at any batch size.
It is reproducible on one device, and is checked against the double-precision walk on states of 10 to 34 qubits: amplitudes within `10⁻⁵` of each amplitude's size, and samples whose probabilities are their own and whose counts follow them.
- **The engine's `cutrank_single` method** runs a `cutrank` job on that table, where the build has the feature and the host has a GPU. `auto` never picks it.
- Its amplitudes carry the circuit's global phase, which is found on the same table.
- The job service, the command line's `metal` feature and the desktop app's `metal` feature build it in.
- The service marks a `cutrank_single` job's energy figure partial, as it does `statevector_single`'s, because the counter does not read the GPU.
- `examples/cutrank_gpu` times it against the host's tables.
- **Speed.** One amplitude of a random graph state on a shared desktop machine, each device timed at its quietest. Either side ran up to five times slower while other work held it.
| widest cut | host, double | host, single | GPU |
|---|---|---|---|
| 23 bits | 0.027 s | 0.024 s | 0.005 s |
| 25 bits | 0.057 s | 0.045 s | 0.019 s |
| 27 bits | 0.18 s | 0.17 s | 0.046 s |
| 29 bits | 1.03 s | 0.54 s | 0.34 s |
A pass of the table runs at about 550 GB/s on an idle device.
Sampling 64 shots of the same states, 16 a batch, with the host at about half its quietest speed:
| widest cut | host, single | GPU, one at a time | GPU, 16 a batch |
|---|---|---|---|
| 16 bits | 0.52 s | 0.62 s | 0.036 s |
| 20 bits | 1.97 s | 0.73 s | 0.067 s |
| 23 bits | 3.87 s | 0.91 s | 0.34 s |
| 25 bits | 7.48 s | 1.82 s | 1.20 s |
The engine samples up to 32 shots a batch, as many as 2 GiB beside the job's first table holds.
### wai-quantum 0.6.11 and wai-quantum-cli 0.1.0: one job engine for the service, the command line and a browser tab
Additive, apart from two refusals and an admission noted below. Every pinned digest is unchanged.
- **`wai_quantum::engine`** (feature `quantum_engine`) is the job service's engine, moved into the library.
- `catalog` plans a job onto the strongest method it admits, and says why.
- `run` carries the plan out.
- `dynamic` runs programs that measure in the middle.
- `receipt` is the service's receipt form: a result sealed over the request and the result in their RFC 8785 canonical text.
- `job` takes a job end to end and checks it.
- `draw` writes a program's circuit as text.
The service imports all of these. A job now gives the same result and the same receipt form whether it runs on the service, on the command line or in a browser tab.
- **The engine builds for `wasm32-unknown-unknown`.** There a result carries no `elapsed_ms`, since a tab has no clock std can read. The multi-seed cut-rank order search runs its seeds one after another and finds the same orders.
- **A noise channel the model does not know is refused** (`catalog::NOISE_CHANNELS`). It was read as no noise, so a misspelt channel ran the circuit noiseless.
- **The job's memory margin is counted once for a program that measures in the middle on the state vector,** as it is for trajectories. It was counted once per state, and the service refused a qubit it could hold.
- **`quantum_receipt::did_key`** gives an Ed25519 key's `did:key`, held to the W3C method's example.
- **`wai-quantum-cli` 0.1.0** is a new crate whose binary is `wai-quantum`. Its commands:
- `run`: planned and run as the service runs it, signed with the user's key;
- `plan`, `lint` and `draw`;
- `submit` to the service;
- `verify`, for any receipt this tool, the service or `lab` signs;
- `key`, `methods` and `completions`;
- `mcp`, which serves these tools to an agent over stdio;
- `lab`, which holds the earlier command's research tools.
It depends on the library alone. The `wai-quantum` bin and the `cli_keys` feature leave wai-rs.
- **Quantum Studio** (`wai/quantum-studio`) is one interface for the browser and the desktop app (`wai/wai-quantum-desktop`). Its editor:
- highlights and completes OpenQASM;
- marks the engine's diagnostics on their lines;
- draws the circuit and shows the plan as the program is typed.
It runs a job in the browser, on the desktop's native engine, or on the service, and checks each receipt where it runs. The desktop app runs each job in a process of its own and seals it with the user's key.
Its assistant uses a local model with the engine as its tools. It checks and runs every program the model proposes before offering it, and marks any figure the engine did not compute.
- **`engine::job`** also splits into `plan_and_run` and `seal`, for hosts that run a job away from their key.
- **`engine::draw::layout`** gives a circuit's layout as data: its columns, and each gate's cells. The text drawing and Quantum Studio's diagram are both drawn from it. wai-web exports it as `engine_layout`, and the desktop app as `layout`.
- **Path search from independent restarts** (`quantum_tn::find_path_restarts`):
- independent searches run side by side, and the cheapest plan is kept;
- the plan is never costlier than one search, and is the same whatever the thread count;
- the engine's `tensor_network` method runs 8 restarts natively and 1 in a tab, and the job option `path_restarts` (1–64) sets the count.
- **Release builds of `wai-quantum`.** A tag `wai-quantum-cli-v<version>` builds the binary on the project's runners and attaches each archive, with its SHA-256, to the forge release:
- macOS, for Apple silicon and for Intel;
- Linux, linked statically;
- Windows.
The same workflow writes a Homebrew formula and a Scoop manifest for the archives.
- **wai-web, feature `quantum_engine`:** `QuantumEngine` (run, plan, methods and signer), `engine_lint`, `engine_draw` and `engine_verify`, for a page. Tested in wasm32 under Node on every method a tab holds.
### wai-quantum 0.6.10: one OpenQASM reader for the service, the command line and the browser IDE
Additive, apart from the old line reader's refusals below. Every pinned digest is unchanged.
- **`wai_quantum::qasm`** (feature `quantum_qasm`) is the job service's OpenQASM reader,
moved into the library unchanged. The service, `wai-quantum run --qasm` and the browser
IDE's import now read a program the same way. Before, the IDE read two registers as
one, skipped a gate on a whole register, and read past what it did not know.
- **`qasm::dyadic_circuit`** gives the exact state vector's circuit for a program whose
measurements all come at the end, and refuses one that measures and then acts, resets
after a gate or uses `if`, with the reason.
- **`quantum_toolchain::from_qasm`**, the older line reader, refuses what it cannot take
instead of dropping the line: more than one register, `reset`, gate definitions,
`if`/`while`/`for`, a gate after a measurement, two statements on one line. Each
refusal names `wai_quantum::qasm`.
- **The IDE binding** (`wai-web`) builds its JSON with `serde_json`, so a quote or a
control character in a program or an error no longer breaks a reply.
`set_signing_key`, `public_key_hex`, `signer_did` and `key_is_demo` let a page sign
receipts with a key of its own; without one it signs with the published demo key and
says so.
- **The command line** signs with `--key FILE`, `$WAI_QUANTUM_KEY`, or this user's key at
`$XDG_CONFIG_HOME/wai/quantum-key` (or `~/.config/wai/quantum-key`). That key is made
from the operating system's random source on first use, with mode 0600.
`--demo-key` keeps the published key, with a note that its receipts show a result is
unaltered, not who signed it.
- **A receipt from the command line or the IDE names its signer by its key's `did:key`**
(`wai::signer_id::did_key`), where it used to carry a label naming no key.
### Live L10b: end-to-end encrypted rooms in the browser
Additive. No record, code point, reason or corpus vector changes.
- **confidentiality §15, a member in a browser.** A page is a member as any other: the
reference's member, sealed objects and codec built for WebAssembly as one module a page loads
for such a room alone, every key in the page's memory and nowhere else. It seals each encoded
frame before its receipt is signed over the sealed bytes, and opens each object through §6
before a decoder sees it; an object of an epoch not yet reached waits (§5.8).
- **§15.2, a room service as the carrier** of a room's MLS messages (§5.1): one log, in the
order the clock took them, of the clock's lifecycle records, its proposals, and every
GroupInfo, commit and Welcome a member posts; the clock checks each post as a member does and
does not carry what it refuses. A member reached through such a log **MUST** apply a
GroupInfo or commit it made only when the log returns it, and drop it when another's commit
for the epoch comes first: §5.5 names the committer from the commit's own proposals, so two
members with different views of the pending proposals can each commit. Before its Welcome a
member follows the group from its handshake messages, as the clock does.
- **§15.3** states what a browser member does not do in this revision: watch without being a
member, ingest by WHIP or WebRTC into such a room, seek back. §1's scope says so.
- **wai-live-web**: `roomDescriptor` gives the descriptor's `recording_mode`, which a page
checks a room's confidentiality record against (§3.3 step 5).
### `wai.live.ads`: a room's cues as server-guided ad breaks, and the break form of `wai.video.ssai`
- **The extension** ([live-ads](extensions/live-ads.md)) registers the record `wai.live.ads`.
The room authority signs an **ad policy**, `WAP1`: the grant the ad decider runs under, whose
funds ceiling is the room's ad budget; the slots of a break; the avail of a break opened with
no planned duration; a preload window; skip and jump restrictions, a skip control and
snapping; and the bases of the asset-list and creative URIs.
- **Breaks from cues.** A room's breaks are its signed `cue` events of `wai.live.events`,
unchanged: an out and its in, or a break of known duration; a chapter opens nothing. A
post-roll is a break its author annotates with a `WAB1` `meta` event right after it, and ends
the program. A verifier refuses an out or a break inside another break, an in with no open
out of its `cue_id` and author, a break of no duration, an out with no planned duration when
the policy has no open avail, an in whose stated duration is not the one that passed, an
annotation that does not read or that follows anything but a break, and any cue after the
post-roll; the room refuses a cue by a principal whose grant does not permit `cue`.
- **Shared manifests.** Each break is one Interstitial `EXT-X-DATERANGE`
(`draft-pantos-hls-rfc8216bis-22` Appendix D) with `CUE`, its duration or planned duration,
`X-ASSET-LIST`, `X-RESUME-OFFSET`, `X-PLAYOUT-LIMIT`, `X-SNAP`, `X-RESTRICT` and
`X-SKIP-CONTROL-OFFSET`, a closing tag at its in, and a Preload date range (Appendix F); and
one DASH alternative-presentation event of ISO/IEC 23009-1:2026, a replacement for a mid-roll
and an insertion for a post-roll, read from the sixth edition's schema and its examples, with
the attributes whose meaning that revision could not state left unwritten. Every value is a
function of the signed cue, its annotation, the policy and `t0`, and the `ID` is derived from
the cue's digest, so every edge writes the same bytes.
- **Asset lists.** The one URI a break names is where each viewer is decided: an HLS asset list
or a DASH list MPD, written exactly, each creative's URI naming it by its hash, filled by
`wai.video.ssai`'s decision over the viewer's pod; a viewer who joins a break running is
filled from what remains of it. An empty fill is `{"ASSETS":[]}` in HLS and no list MPD in
DASH.
- **`wai.video.ssai` gains form 2, the break form.** A receipt names the room, the break by its
cue's digest, the session, the viewer's offset, the asset list's SHA-256 and format, the
avail, the slots and the funds the decision ran with, beside the stitched form's pod hash,
impressions, Merkle root, billable spend and energy figure. It is carried as JSON, signs
under its own domain (`wai:video-ssai-break-receipt`) and shares the identity domain, so the
figure profile `wai:video-ssai-receipt-id` names either form. Every stitched receipt keeps
its bytes, and its legacy pin is unchanged.
- **Verification end to end**: the room, the policy, the schedule, the renderings, then each
receipt: its own rules, the decider's key and grant reference, the break, the decider grant's
window and revocation, the pod hash, the decision recomputed, the asset list's digest, one
receipt per session and break, and the spend of every receipt together within the decider
grant's ceiling (prices in millionths of a minor unit of its currency).
- **Twenty refusal reasons**, from `ad_policy_malformed` to `funds_exceeded`.
- **Reference and corpus.** `wai-rs` feature `live_ads` (`src/live_ads.rs`), and
`SsaiBreakReceipt` in `src/wai_video_ssai.rs`. `live-conformance/ads/`, written from fixed
seeds by `wai_live_ads_vectors`: a live break with a viewer at its start and one who joins it
running, a mid-roll under restrictions with an empty fill, a program closed by a post-roll,
chapters with a planned out, a gap, and one refused room per reason; each accepted room's
HLS tags a file of its own. Checked by `tests/live_ads.rs` and by
`live-conformance/live_ads_verify.py`, a second implementation that shares no code with
`wai-rs`, verifies each room with `live_rooms_verify.py`, writes every tag, event and asset
list itself, and requires every mutated copy of the corpus to fail. The registry lists the
record and the magics `WAP1` and `WAB1`.
### `wai.live.watermark`: forensic watermark sessions, matched and disclosed one at a time
- **The extension** ([forensic-watermark](extensions/forensic-watermark.md)) registers the
record `wai.live.watermark`, for a room's Lane A delivery (SPEC §6.5). The edge that chooses
each segment's A or B variant for a viewer, as ETSI TS 104 002 has an edge choose it, signs a
**session record**, `WWS1`, when the session ends: the room and the content (a slot or a
publisher, and its media kind), the span, the KIDs of the content keys the variants were
encrypted under, the TS's watermark token by its SHA-256, the token's vendor and pattern
length, how the edge had the pattern (in the clear, decrypted, or derived by the vendor's
core), a bitmap of the positions it sequenced and the variant at each, its counts of
sequenced and unmarked segments, a salted commitment to the session's grant and principal,
where it keeps the record, and when it is deleted.
- **The token** is the TS's CWT, read as the TS and RFC 8392, RFC 8949 §4.2, RFC 9052 and RFC
9053 have it: deterministically encoded CBOR, a COSE_Mac0 under HMAC 256/256 or a COSE_Sign1
under ES256, optionally in the CWT tag, and the claims `wmver`, `wmvnd`, `wmpatlen`,
`wmsegduration`, `wmpattern` (in the clear, or a COSE_Encrypt0 or COSE_Encrypt) or `wmid`,
`wmopid` and `wmkeyver`, with `exp` and `iat`. A form the RFCs forbid is `token_malformed`,
and one they allow that this revision does not read (a floating-point date, an untagged or
nested message, another algorithm) `token_unsupported`. The record names the token by digest
and does not carry it: a token can carry `sub` or a vendor's `wmid`, and can be presented
again until it expires. The token's MAC or signature is the edge's to check.
- **No viewer in the record.** It names no grant: every participant holds a room's grants, and
a grant names its principal. Only the room authority, to which the edge gives each record's
opening, can link a session to a principal. Records, tokens and openings are kept within the
room's region pin and its retention bound.
- **Matching.** A claimant signs the variant sequence it recovered from a copy, `WWQ1`, with
its erasures. A session agrees when it sequenced every stated position with the stated
variant; a match is the one agreeing session among the records given (`no_match`,
`ambiguous_match` otherwise). The TS leaves detection's statistics to its vendors, so the
rule has no tolerance and no threshold, and states how many positions were stated.
- **Disclosure.** For one matched session the room authority signs a `WWD1` that opens its
commitment; a third party checks it against that record, the sequence and the grant, and
learns nothing of other sessions.
- **Lane A only.** A record names at least one content key (`not_protected`), and a room with
a confidentiality record has no edge that can mark it (`e2ee_room`): its objects' AEAD binds
each to its track, group and object.
- **Refusal reasons**, from `record_invalid` to `disclosure_expired`. jwp-receipts §7.5
registers the key-log authority `live/watermark` for an edge.
- **Reference and corpus.** `wai-rs` feature `live_watermark` (`src/live_watermark.rs`), with a
deterministic-CBOR reader of its own and no new dependency. `live-conformance/watermark/`,
written from fixed seeds by `wai_live_watermark_vectors`: tokens read and refused, an
edge's records of four sessions with a token in each mode, sequences matched, unmatched and
refused, disclosures accepted and refused, one control for each reason. Checked by
`tests/live_watermark.rs` and by `live-conformance/live_watermark_verify.py`, a second
implementation that shares no code with `wai-rs`, requires every mutated copy of the corpus
to fail, and requires the corpus to catch a verifier that leaves out any one of its checks.
The registry lists the record and the magics `WWS1`, `WWQ1` and `WWD1`.
### Live L7b: the DVR window, the seek power, and WMH1 version 2
Additive: no existing record changes its bytes, and every existing corpus vector keeps its
records.
- **live-recording §8** gains what a relay that keeps a **DVR window** of a room's tracks
does with it (§8.2 to §8.5): a window by age (at most 24 hours) and by bytes, evicted
oldest first by whole group, each media group with its receipts group; a FETCH answered from
the cache and the window together, groups in the requested order; what was evicted marked
with an End of Unknown Range (MOQT §11.4.1.2, `0x10C`), never with an unmarked gap or an End
of Non-Existent Range, since an evicted object existed; an object that cannot be served
inside a group marked from the group's start, since -22's End of Range moves to a later
group; a harvest window whose end lies ahead as a SUBSCRIBE with that range and a fill;
decisions read from the room's events tracks withholding what they deny or redact, never
replacing it; every object checked again against its receipt after a restart. §8.6 maps the
window onto DASH's `timeShiftBufferDepth` and an HLS live playlist. §8.1 is the take
addressing as before. §8.2 also says what a relay never keeps: a room with a confidentiality
record, known by its first sealed object. The relay keeps nothing of the room from then on,
deletes what it kept of it before, and remembers the room across a restart. confidentiality
§9 points to it.
- **live-rooms**: the **seek** power, bit 4 of `powers` (§3.3), and **§10.6 Seeking back**:
a FETCH, or a fill, that starts below a track's live edge is served only to a grant with the
power, and refused `UNAUTHORIZED`, `seek_not_granted`, otherwise. Roles and grants may now
set bits 0 to 4. `wai-rs` `live_room::seek_check`; the corpus gains a `dvr` room, three
refusals (a grant's seek beyond its role, power bit 5 in a grant and in a role) and a
`seek` section of requests served and refused, which `live_rooms_verify.py` checks too.
- **live-metering**: `WMH1` **version 2** (§3.9) adds a hop's DVR figures before `sig`: the
objects and bytes served from its window, those it wrote to it, the bytes it held in
byte-seconds, and the objects it dropped because they did not verify; with their rules
(§3.6) and bound `B₂(M) = 600 + 180 × (M + 2)` (§3.7). A hop with no window signs version 1.
The receipt stays version 1 and does not sum the figures. The corpus gains a version 2 relay
record and four refusals; `live_metering_verify.py` reads both versions.
### wai-quantum 0.6.9: cut-rank amplitudes with the circuit's own global phase
Additive. Every pinned digest is unchanged.
- **`PhasedTableau`** (`quantum_tableau`) is a stabilizer tableau with its global phase.
- It tracks one basis state `r` of the support and its exact amplitude
`⟨r|ψ⟩ = 2^{-k/2} e^{iπm/4}` through every gate.
- Paulis, phase gates, CX, CY, CZ and SWAP move `r` and the phase directly.
- For a Hadamard on qubit `a`, the amplitude of `r ⊕ e_a` comes from the stabilizer
whose X part is `e_a`, found by reducing the stabilizers (about `n³/64` word
operations). `√X` is `H S H`, exactly.
- Tested against the dense state vector after every gate of 40 random circuits
(2 to 8 qubits, over 2,000 gates). A mutant of the Hadamard update is caught.
- **`ProductMeasured::with_last`** keeps the graph form, order and steps with other closing
gates. **`ProductMeasured::scale`** multiplies every amplitude by a factor, folded into
one qubit's weights. Tested against fresh builds.
- **The service's `cutrank` method** returns amplitudes with the circuit's own global
phase.
- The factor is `e^{iΘ} a/b`. Here `a` is the tracked amplitude of the Clifford part,
`b` is the graph form's amplitude at the same basis state (closing gates set to the
identity), and `e^{iΘ}` is the phase the lowering to Clifford gates drops. That
phase is read gate by gate from each gate's matrix against its lowering's.
- Past about a minute of tracking (counted from the Hadamards), the result says
`global_phase` is not fixed, as before.
- `auto` now tries `cutrank` for amplitudes too, before `tensor_network`.
- Tests compare amplitudes with the state vector's directly (no phase aligned). The
circuits include phase-carrying lowerings: `rz`, `rx`, `ry`, `u`, `p`, `cp`, `rzz`,
`rxx`, `iswap`, `sx`, `sxdg`, `cy`, `swap`, `crz` and `cu`. Two mutants are caught:
the lowering phase dropped, and the tracked amplitude ignored.
### wai-quantum 0.6.8: threads only where they pay, and a cut-rank search that recounts only what moved
Additive. No result changes a bit: every kernel path gives the same bits at any thread count,
and every pinned digest is unchanged.
- **State-vector passes** (`quantum_sv`): a gate, a slice pass or a fixed-chunk sum now spawns
at most one thread per `2¹⁸` amplitudes. Each pass spawns its threads afresh, and below
that size a thread costs more than its share. On a shared 32-core host, a gate from 14 to
18 qubits ran 5 to 14 times slower with a thread per core than on one thread. A count that a
test forces is still taken as it is, so the split paths stay tested at small sizes. The
ignored `gate_timings` test prints the times.
- **The cut-rank search** (`ProductMeasured::anneal_order`) moves one qubit at a time. Seven
moves in eight go at most four places, and the rest anywhere. Only the cuts between the
two places are recounted, so a move costs about a ninth of a full recount. On the 70-qubit
graph-state circuit, 300 000 moves reach a 29-bit widest cut in about six seconds. In
tests, every move's recounted cuts are checked against a full recount.
- **The service's `cutrank` method:**
- The planner makes one short search, enough to bound the job's memory and work.
- The run searches up to 300 000 moves from each of four seeds, on threads of their own,
with the planner's order among the candidates.
- It keeps the cheapest order whose widest cut is no wider than the planner's, so a job
stays inside the memory it was admitted with.
### wai-quantum 0.6.7: tensor networks that split controlled gates and fix projections
Additive. `Network::amplitude` and `Network::simplify` are unchanged, so every pinned digest is too.
- **`Network::amplitude_split`** (`quantum_tn`) splits each gate on two or more qubits at
every qubit it is block-diagonal on: a control, or a qubit it is diagonal on. There
`G = Σ_a |a⟩⟨a| ⊗ G_a`, so that qubit gets the delta `δ(out, in, k)` on its wire, and the
blocks `G_k` keep the rest, joined by a bond of dimension 2. Only zeros of the matrix are
read. A cut through a controlled-Z or controlled-X then costs 2, not 4.
- **`Network::reduce`** fixes every index that some tensor allows only one value of: each
tensor holding it keeps its entries at that value and drops the index, until nothing
changes; then it simplifies. Only zeros are read and entries selected. Input and output
projections run through diagonal gates and split deltas, so a controlled-Z beside a
projected output becomes scalars. Open indices are never fixed.
- On 61-qubit controlled-Z circuits of depth 8, 12, 16 and 20, the search (64 trials) finds
single-amplitude trees of log₁₀ 5.35, 8.25, 11.22 and 14.26 multiply-adds on split, reduced
networks, against 8.03, 12.46, 16.09 and 19.64 on whole-gate, simplified ones.
- `examples/tn_cost`, the CLI's `wai-quantum tn` (`--whole-gates` for the old networks) and the
service's `tensor_network` method build split, reduced networks.
- Tests: split networks, reduced or not, and reduced whole-gate networks give the state
vector's amplitudes on circuits of controlled-Z, controlled-X both ways, a controlled
random unitary, random diagonal gates, Toffoli, CCZ and random two-qubit unitaries, with
outputs projected and open. CZ and CX split and a random unitary does not; CX splits at its
control. On a 4 × 5 grid of controlled-Z, the split tree costs under an eighth of the
whole-gate one. Three mutants are caught: the delta's entry, the block-diagonal test, and
the value kept by `reduce`.
### wai-quantum 0.6.6: Clifford circuits measured in a product basis, exactly, by cut-rank
Additive. Every pinned digest is unchanged.
- **`Tableau::graph_form`** (`quantum_tableau`) writes a stabilizer state as local Cliffords
(`H^h S^s Z^z` per qubit) on a graph state. Tested by rebuilding the state from the form
over 40 random circuits.
- **`quantum_cutrank`** (a new feature, in `full`) sums a Clifford circuit's state measured
after one closing layer of single-qubit gates at any angles:
`⟨x| ⊗ V_q |ψ⟩ = 2^{-n/2} Σ_z ∏_q W_q[x_q][z_q] (−1)^{Σ_{a~b} z_a z_b}`.
- The sum goes one qubit at a time along an order, in a table of `2^{cut-rank}` entries.
Each qubit is an in-place pass: a merge where the boundary space loses a dimension, the
qubit's signed terms, and a doubling or an XOR-shifted sum for its edges ahead.
- The passes depend on the graph and the order alone, so they are planned once per state.
- By Parseval, the table's norm is a prefix's probability, so the same passes sample
exactly: `amplitude`, `probability`, `sample`.
- `Precision::Single` stores the table in `f32` with `f64` arithmetic: half the memory and
traffic. `anneal_order` searches orders for less work (`work` = Σ 2^cut) and never
returns a worse one.
- Every reduction is summed in fixed chunks, so a result is the same bits at any thread
count.
- Tests:
- amplitudes equal the state vector's in probability and relative phase (30 circuits up
to 12 qubits);
- forward, reverse and shuffled orders agree (24 to 54 qubits);
- single-precision tables agree with double;
- samples follow the exact law and are seeded;
- annealing lowers the work and keeps the amplitudes;
- cut-ranks are the entanglement of GHZ and product states.
- Five mutants are caught: the merge's signs, the sampling choice, the doubling's sign,
the fold's coefficient, and the graph form without its S.
- **Examples:**
- `cutrank_qasm` reads an OpenQASM 2 Clifford bulk with a closing single-qubit layer, and
prints cut-ranks, annealed orders, amplitudes and exact samples. On a 70-qubit
graph-state sampling circuit, an annealed order keeps every cut at 29 bits or fewer: one
amplitude in about half a second, one exact sample in 1.1 to 1.5 s, with a largest
table of 4 GiB.
- `patch_xeb` scores measured shots of a patched circuit by patched cross-entropy, each
patch simulated exactly on the dense state vector.
- `tn_cost` prints the contraction cost of one amplitude as `find_path` finds it.
### wai-quantum 0.6.5 and the service: programs that measure in the middle
Additive. Every pinned digest is unchanged, and a program that measures only at the end
runs exactly as before.
- **`Tableau::measure`** (`quantum_tableau`) measures a qubit of the wide tableau and leaves
the state the outcome selects. A random outcome is the caller's to choose, so a branch
can be followed on purpose. The anticommuting stabilizer is multiplied into every other
row at once, each row's phase summed mod 4 in two bit planes. `Tableau::is_random` says
in one column scan whether an outcome is random.
- Tested against the state vector's projection, Pauli string by Pauli string, over 60
random circuits of six measurements each.
- On wide tableaux, measuring every qubit lands inside the support held before, with one
random outcome per dimension.
- Three mutants are caught: signs kept, the Y phase swapped, the destabilizer not moved.
- **`StateVector::probability_one` and `StateVector::collapse`** (`quantum_sv`): a qubit's
probability of reading 1, summed in fixed chunks, and the state a reading leaves.
- **The service runs dynamic programs.** It reads measurement and `reset` anywhere,
`if (c[0]) …`, `if (!c[0]) …`, `if (c == 3) { … } else { … }`, OpenQASM 2's
`if(c==n) gate;`, and `bit b = measure q;`.
- Such a program runs on `statevector` or `stabilizer`. Its shots split at each
measurement and reset as independent shots would, and each outcome runs on in its own
copy of the state.
- Expectation values are the exact mixture over the branches, of the state before the
final measurements.
- Tests: teleportation gives ⟨Z⟩ = cos θ and ⟨X⟩ = sin θ exactly. It agrees between the
tableau and the state vector, reset and re-measurement behave, and a bit-flip syndrome
corrects the flip. Four mutants are caught.
- Loops, noise in such a program, and amplitudes of one are refused with their reasons.
### `wai.live.moderation`: review policies, signed decisions, and statements of reasons
- **The extension** ([live-moderation](extensions/live-moderation.md)) registers the record
`wai.live.moderation`. The room authority signs a **review policy**, `WMP1`, chained to the
one it replaces and in effect from a room-clock instant: which event kinds and media are held
for review before delivery (pre) or reviewed after it (post), the reviewers, each one's
deadline and fallback (allow, deny or hold), the trusted flaggers it recognises, the reason
codes with their ground, provision or clause and database category, the territory, and the
redress text. Every policy keeps the room descriptor's review field (live-rooms §3.6).
- **Decisions.** A moderator's verdict is a `decision` event of `wai.live.events`, unchanged,
followed in its chain by a `meta` event carrying the **statement**, `WMS1`: its target (an
event, or a media object such as a recorded chunk named by its leaf), the target's `prev`,
the decision it supersedes, its basis (own initiative, a notice, a complaint, the policy's
fallback), its use of automated means in detection and in the decision, and its ground,
explanation and facts. Decisions on one target form a line by digest; an appeal's outcome
supersedes the decision complained of. The room's clock signs each fallback the policy
applies, at or after the deadline. A decision by a principal without the moderate power is
refused by the room verifier (`kind_not_permitted`).
- **Notices and complaints**, `WMN1`, are signed by whoever submits them: a notice names an
item, its ground and the submitter's statement and contact (Article 16(2)); a complaint
names a decision (Article 20). The contact is never exported.
- **Review and redaction.** Each item's state over time (held, pending, allowed, denied,
redacted, or the fallback at its deadline), what a relay holds and delivers, and what a sink
shows. A restricted item is withheld, never replaced: a gap it leaves in its author's chain
is explained by the moderators' signed statements, walking the chain back through each
withheld event's `prev`; a clip over a redacted chunk keeps its range proof, withholds the
chunk's bytes (`redaction_not_applied` when they are given), and presents the chunk again
once a later decision restores it. A recording package under moderation is specified only.
- **The statement-of-reasons export.** Each restricting decision maps onto one record of the
DSA Transparency Database's statement-of-reasons format (the schema in force from
1 July 2025, which its documentation calls API v2), written byte for byte: every member,
its source in the WAI records, closed lists and length limits, the sixteen categories, the
content types, and every Article 17 element with WAI's field for it. It carries no
principal key, notice contact or content; `source_identity` is never written. A record that
cannot be written is refused `sor_incomplete`, naming the member.
- **Twenty refusal reasons**, from `policy_malformed` to `redaction_not_applied`.
- **Reference and corpus.** `wai-rs` feature `live_moderation` (`src/live_moderation.rs`);
live-recording's clip verifier gains `verify_clip_exempting`, which `verify_clip` calls
exempting nothing, so its corpus is unchanged. `live-conformance/moderation/`, written from
fixed seeds by `wai_live_moderation_vectors`: a pre-moderated chat room whole and as a
subscriber receives it, a notice and a trusted flagger's notice with their decisions, an
appeal that reverses a decision, live-recording's lesson session with a chunk redacted and
then restored, one refused room per reason, an export refused, and clips of the redacted
take; each export a file of its own. Checked by `tests/live_moderation.rs` and by
`live-conformance/live_moderation_verify.py`, a second implementation that shares no code
with `wai-rs`, verifies each room and clip with its corpora's second implementations, writes
every export itself, and requires every mutated copy of the corpus to fail. The registry
lists the record and the magics `WMP1`, `WMS1` and `WMN1`.
### wai-quantum-service: every job's energy, signed with its result
- **What a result carries.** `energy` holds the CPU energy of the job's worker process, from
its start to its exit, inside the body the receipt seals. It is given as `joules_micro`,
with the acquisition class `OnChipCounter`, a declared uncertainty
(`relative_ppm`, `window_us`) and the counter's filtering state, in
[energy-measurement](extensions/energy-measurement.md) §2's names. It also states its
coverage and what it excludes.
- **How it is read.** On macOS, the service reads the worker's kernel count
(`proc_pid_rusage`, `ri_energy_nj`, plus energy system services billed to it) after the
worker has exited and before it is reaped, waiting with `waitid(WNOWAIT)`. An exited
process has left every core, so the count is complete. The declaration covers the
rounding to whole microjoules, and meets the §2.3 floor at every size.
- **What it does not claim.** A `statevector_single` job runs on the GPU, which the counter
does not read, so its figure is `partial`. A job the service cannot read is
`{"unmetered": why}`, never zero joules.
- **Agents.** The MCP summary states the figure and its coverage.
### `wai.live.steering`: each client's pathway, chosen by the joules its hops signed
- **The extension** ([energy-steering](extensions/energy-steering.md)) registers the record
`wai.live.steering`. The operator signs a **steering policy**, `WSP1`: the pathways (each a
pathway ID of the content description or a clone of one, through another host), each
pathway's hops (edge or interior, a hop in one pathway only), the quality floor, the latency
ceiling, each pathway's capacity and minimum share, the hysteresis margin, the TTLs, and the
period and window over which the hops' per-hop records (`WMH1`) are read. The new key-log
authority `live/steer` lets an operator key sign policies and decisions.
- **The evidence.** A pathway's figure is its hops' joules over the window, per media byte its
edge hops delivered or per microsecond of delivery at the floor tier or above, with the
records' common acquisition class. The window ends on the last period past a lag, and every
hop's records must tile it exactly, so every pathway is read over the same span and a
record left out makes the pathway **stale**, never cheaper. A pathway with an unmetered hop,
hops of two classes, stale records or no delivery has **no figure**: a decision that gives
one a figure is refused `unmetered_as_zero` or `classes_summed`.
- **The rule.** Pathways are ordered by class (calibrated instrument, counter, model,
estimate), within a class by figure, compared by exact products, and then by ID; a pathway
with no figure comes after every pathway with one, so not measuring is never rewarded. A
client with a short buffer or at its quality floor is held; a session's draw, fixed for the
session, places it on a pathway by that pathway's minimum share whatever its evidence; a
client moves off its pathway only for a figure lower by more than the margin. The output is
the priority, the TTL and the reason the first pathway is first.
- **The steering decision**, `WSD1`, names the policy, the records by digest, the client's
report (read from `_HLS_pathway` and `_HLS_throughput`, `_DASH_pathway` and
`_DASH_throughput`, and CMCD's `bl` and `mtp` in the query or the `CMCD-Request` header), the
server's session and loads, each pathway's evidence and limits, and the output; a verifier
recomputes all of it; ten refusal reasons.
- **The mappings.** The steering manifest of `draft-pantos-content-steering-04`, which HLS
(`draft-pantos-hls-rfc8216bis-22` §7) and DASH (ETSI TS 103 998 V1.1.1) read, written byte
for byte with `PATHWAY-CLONES`, a `RELOAD-URI` carrying the session, and one member,
`science.transaction.wai-decision`, naming the decision's URI, which players ignore; the
`EXT-X-CONTENT-STEERING` tag and the DASH `ContentSteering` element.
- **Reference and corpus.** `wai-rs` feature `energy_steering` (`src/energy_steering.rs`, with
an RFC 9651 parser of its own); `live-conformance/steering/`, written from fixed seeds by
`wai_energy_steering_vectors`, its figures synthetic, and checked by
`tests/energy_steering.rs` and by `live-conformance/energy_steering_verify.py`, a second
implementation that shares no code with `wai-rs`, reads the room and the per-hop records
with their corpora's second implementations, recomputes every report, figure, decision and
manifest, and requires every mutated copy of the corpus to fail. The registry lists the
record and the magics `WSP1` and `WSD1`; jwp-receipts §7.5 lists `live/steer`.
### Live ingest and egress: WHIP and WHEP onto a room's tracks, E-RTMP v2 and SRT mapped
- **The extension** ([live-ingest](extensions/live-ingest.md)) binds the contribution and
playback protocols of standard encoders and players to the tracks of a room. No record
and no capability of its own; no new transport and no new token format.
- **Admission (§3).** A WHIP or WHEP bearer token is a room grant, `WRG1` in base64url. A
gateway checks it against the room's descriptor as live-rooms §4.3 does, then its window,
the room's revocations, its origins and its single-use nonce, and answers `401` with
RFC 6750's challenge; a good grant without the power a request needs (publish for each
media kind of a WHIP offer, subscribe up to the layer for WHEP) is `403`
`insufficient_scope`. Every request with a grant carries a proof of possession of its key
(live-rooms §10.5.3): `WAI-Room-Proof` over a single-use `WAI-Room-Challenge`, refused with
live-rooms' `pop_*` reasons.
- **Tracks and objects (§4, §5).** A WHIP session publishes a layer-0 media track per kind,
each with its receipts track, and the owner's catalog with a receipts track of its own.
Each frame is one object, one `WAI1` envelope in its capability's framing: an H.264 access
unit in Annex B, a VP8 or VP9 frame in one-frame IVF, an AV1 temporal unit in SPEC §5's
framing, one Opus packet. The objects of a live track, from a group's first, are one
stream of the codec. Video groups begin at keyframes; audio groups are fixed windows on
the room clock. A track's first group ID is its first frame's room-clock time, so a
publisher that reconnects continues above the IDs it used.
- **Time (§6, §7).** The manifest's new `live` member carries the frame's room-clock time,
its RTP time and, on a group's first object, the grant reference of live-rooms §4.5. RTP
time maps to the room clock from the track's first frame, re-anchored past `max_skew_ms`,
so every frame's time lies within that bound of its arrival at the gateway.
- **Receipts (§8).** The gateway signs each group's receipts as the first hop, after the
group's last object, chained per track on an alias derived per session. They state what
the gateway received, not what the publisher signed.
- **WHEP (§9).** An egress session reads the owner's catalog once its receipt verifies,
joins each track at its latest group, and verifies every object (its receipt's group and
Object ID, the signature under a trusted first hop, the chain, the inclusion proof, the
envelope) before the frame reaches the player; what fails is dropped and counted. A WHEP
player sees no receipt; a WAI-aware player subscribes over MoQ and verifies itself.
- **Mapped, not yet implemented (§12, §13).** E-RTMP v2 multitrack: `capsEx`, the stream
name as the token, tracks by the grant's profile layers, FourCCs to capabilities, the
reconnect request. SRT 1.5.7: a one-time session ID from an HTTP exchange in the stream
ID's `s` key, rejection codes from the HTTP statuses, MPEG-TS stream types to
capabilities, latency.
- **How an ingest relates to a take (§15)** and **to metering (§16)**: an ingest is the live
tier, never a `single-take`; a device that speaks capture binding still commits its take
beside its WHIP session. Each gateway session ends with a signed record in the shape of
live metering's per-hop record.
- **Reference.** `joule-moq-ingest` in `joule-code/joule-code-rs/crates/`, on `str0m`
(sans-IO WebRTC, its pure-Rust crypto backend). Its reading of rooms and grants is held to
`live-conformance/rooms/`.
### `wai.video.vp8` and `wai.audio.opus.packet`: the media of a WebRTC ingest
- **Why.** A live ingest of WebRTC media hands WAI the codecs WebRTC endpoints send.
VP8 is mandatory to implement in every WebRTC browser, and in every WebRTC
non-browser that sends or receives video (RFC 7742 §5); a live track carries
Opus one packet per object, not as an Ogg stream. SPEC §5 *Broadcast and
streaming codecs* registers both, 2026-10-18, with no edition named.
- **`wai.video.vp8`** (RFC 6386): VP8 frames in the IVF frame container with
FourCC `VP80`, a 32-byte file header and a 12-byte header before each frame, the
framing `wai.video.vp9` uses. Royalty-free, as VP9 (the reference source under a
BSD licence with a patent grant). `StandardUnclassified`: RFC 6386's
decoder-conformance text was not read, so the registry promises no byte identity;
VP8 shares VP9's framing, not its class.
- **`wai.audio.opus.packet`** (RFC 6716): one Opus packet (§3), its TOC byte first,
with no container, decoded with an Opus decoder (§4) of one or two channels at any
rate it supports. It carries no pre-skip and no output gain, which are Ogg Opus's
(RFC 7845 §5.1); `wai.audio.opus` remains the capability for a stored stream. A
different framing takes a string of its own (§8), suffixed as
`wai.video.av1.lossless` is. Royalty-free; `StandardTolerance`, as `wai.audio.opus`
(RFC 6716 §6). How consecutive objects of one live track decode is the live
binding's rule ([live-ingest](extensions/live-ingest.md)).
- **Neither is a default.** Both are standalone, `Lossy`, sink-supplied, and outside
the floor and the recommended set; the native sink advertises neither. Neither is a
base of `wai.video.lcevc`. The codec glossary links its VP8 and Opus entries to
them, and the research registry records the licence and replicate basis of each
(`research/media-codec-registry.md`, ¹⁵). The registry export, the vocabulary and
the table in `REGISTRY.md` are regenerated.
### Live delivery hardening: receipt chains, the receipts' binary form, room track names, proof of possession
- **A join's fill from the chain's first group.** [jwp-receipts](extensions/jwp-receipts.md)
§3 step 3: below a join's anchor a group with a `null` parent is the chain's true start,
`parent_not_held` and held as history until the group above it links back to it, not a
second chain head (`chain_break`). A `null` parent outside such a gap, and a fork, are
still `chain_break`. `wai-rs`, `jwp_verify.py` and two corpus vectors
(`chain_join_fetch_from_group_0`, `chain_join_second_start_after_the_fill`, §5.9).
- **The receipts' binary form.** jwp-receipts §2.7 (new): group and object receipts, and a
group's receipts together (`WJR1`), as bytes. A group is its canonical bytes (§3 step
3), its link hash computed over them as received; an object its members in fixed widths.
Reading yields the JSON form's members, and §3 applies to them unchanged; §2.7.4 says
what does not read. [moq-streaming-format](extensions/moq-streaming-format.md) §4.1: a
receipts-track object carries this form, not JSON. 20 vectors (procedure `binary`,
§5.12), read and written byte for byte by `wai-rs` (`jwp_delivery::binary`),
`jwp_verify.py` and `joule-moq`.
- **Room track names.** [live-rooms](extensions/live-rooms.md) §6.1 names a media track's
receipts track (its name followed by `_receipts`) and a publisher's catalog track (the
room and owner fields, Track Name `catalog`); §6.2 ties the catalog's
`wai_receipts_track` to it; §10.2's `moqt` scopes admit both. The rooms corpus's scopes
change accordingly.
- **Proof of possession.** live-rooms §10.5 (new): a grant is public, so every binding
honours it only beside a `WRP1` proof by its principal over a fresh challenge the
verifier chose and a context naming the endpoint (a verb and `//authority/path`). Over
MOQT the relay's SETUP carries the challenge (`GRANT_CHALLENGE`) and the client's the
grant and the proof (`GRANT_PROOF`); a SETUP whose grant has no proof that passes is
closed `UNAUTHORIZED`. Over HTTP (WHIP and WHEP's Bearer token) the proof rides
`WAI-Room-Proof`, and single-use challenges `WAI-Room-Challenge`. §10.5.4's checks
add eight reasons to §11 (`pop_missing` to `pop_wrong_context`); the CAT mapping and
§13 follow. The rooms corpus gains 17 proofs, MOQT and HTTP; `live_rooms_verify.py`
checks them from the text.
- **Registry.** Wire identifiers `WJR1`, `WRP1`, the Setup Options `GRANT_CHALLENGE` and
`GRANT_PROOF`, and the HTTP fields `WAI-Room-Proof` and `WAI-Room-Challenge` (two new
kinds, `setup-option` and `http-header`), the last four proposed and not registered.
### `wai.live.metering`: a room session's units and joules per hop
- **The extension** ([live-metering](extensions/live-metering.md)) registers the record
`wai.live.metering`. Every hop that carried a room session, a relay, a compositor or an
ingest gateway, signs one **per-hop session record**, `WMH1`: per track and direction the
objects and bytes it carried and the objects it dropped by reason (delivery timeout,
refused at the door, duplicate, queued at the end) with those whose receipts it
forwarded; its subscriber sessions' time by layer class; the events it delivered; its own
energy with the acquisition class of energy-measurement, or the unmetered marker; and the
root of a **ledger**, one entry for each grant its sessions ran under, with that grant's
joule and funds ceilings. Media tracks are counted one by one, events tracks and every
other track of the room together, subscribers by layer class, so a record is at most
552 + 180 × (*M* + 2) bytes, *M* the media tracks the room published, whatever the
audience; a record past that is refused before it is read.
- **The room-session receipt**, `WMR1`, signed by the room authority or by a key the
origin's key log lists with the new authority `live/meter` (and `energy/<class>` for each
class it totals): a key-log authority rather than a grant power, since a receipt states
every principal's consumption and a grant's powers are live-rooms' closed set. It names
the per-hop records, the takes and the composition receipts it aggregates and states
publisher-seconds, subscriber-seconds by layer class, composition-seconds, events sent
and delivered and recorded-seconds (the capture each device committed while capturing),
every hop's joules with its class, one total per class and never one across classes
(refused `classes_summed`), and each grant's consumption: its units, a share of each
metered hop's figure by the bytes it moved, labelled with that figure's class, its funds
under a stated tariff, and its verdicts against its ceilings, committed by a consumption
root and every grant past a ceiling named. A verifier recomputes every number from the
records it names; eighteen refusal reasons.
- **QoE.** Clients' delivery-telemetry observations, as CMCD version 2 Event-mode reports,
ride the CMCD event timeline of `draft-wilaw-moq-cmcd-event-timeline-00` (`T` the
report's `ts`, values as strings, `sf` and the custom keys not carried), or CTA-5004-B's
`application/cmcd` batch, whose line syntax the reports already have. Only a cell of at
least fifty sessions enters the receipt, suppressed below that; the observations never
leave the session and the room's service.
- **C2PA.** `c2pa_emit` (with `live_metering`) writes a receipt into a real signed manifest:
`c2pa.environmental-sustainability` with `energy_kwh` only where one class covers the
session whole, GHG and water only when the caller supplies them with their method, and
always the companion `science.transaction.wai.live.metering` with the totals by class.
- **The reference relay** signs the per-hop record of a room from its session summaries
(joule-code's CHANGELOG), and the corpus holds a record it signed in its test.
- **Reference and corpus.** `wai-rs` feature `live_metering` (`src/live_metering.rs`;
`capture_take::find_take` exposes capture-binding §9.2 to it); `live-conformance/metering/`,
written from fixed seeds and the relay's recorded record by `wai_live_metering_vectors`, and
checked by `tests/live_metering.rs` and by `live-conformance/live_metering_verify.py`, a
second implementation that shares no code with `wai-rs`, verifies rooms, takes and
composition receipts with their corpora's own second implementations, recomputes every
unit, total and consumption entry, and requires every mutated copy of the corpus to fail.
The registry lists the record and the magics `WMH1` and `WMR1`.
### wai-quantum 0.6.4: noisy Clifford circuits by Pauli frames, the state vector on the GPU, and the density matrix by superoperators
Additive. Every pinned digest is unchanged.
- **`DensityMatrix::evolve`** (`quantum_noisy`): the exact noisy state on up to 16 qubits
(`MAX_EVOLVE_QUBITS`), against 12 for `DensityMatrix::run`, which stays as the referee.
- Each gate goes on `ρ`'s rows, its conjugate on the columns. Each channel becomes one
superoperator `Σᵢ wᵢ Kᵢ ⊗ Kᵢ*` on its qubits' row and column bits, in `run`'s order.
- A gate and its noise fuse into one gate on at most four of the vector's qubits, applied
in the state vector's tiled passes. A gate therefore costs one pass and no copy of `ρ`.
- **Checked:** against `run`, every entry within 10⁻¹² on circuits with every channel and
a Toffoli. At 14 qubits, past `run`'s size, against branching trajectories within four
standard errors.
- **Measured** on a 16-core desktop processor: 12 qubits in 0.53 s against 158 s for
`run`, and 10 qubits in 39 ms against 4.1 s. 15 qubits with 132 noisy gates take 49 s.
- **The service's `density_matrix`** runs on it, up to 16 qubits within a job's memory and
a work cap. `auto`, with noise, still tries it first.
- **`quantum_spd::expectation_capped`**: the sparse-Pauli engine held to a number of terms.
Whenever a rotation leaves more, the threshold doubles and the sum is truncated again
until it fits, and everything dropped that way joins the bound. A run that never reaches
the cap is the uncapped run, bit for bit.
- **Checked:** on the 127-qubit kicked-Ising circuit, a cap above the peak changes no bit.
A cap a tenth of the peak stays within its bound of the uncapped value. On a small
register, the bound covers the exact error.
- **The service's `pauli`** holds as many terms as the job's memory allows (`max_terms`
can lower it). A sum that would have outgrown the job now finishes at a coarser
threshold, reported as `threshold_reached`, with its bound.
- **The tensor-network executor blocked and threaded** (`quantum_tn`). Each pairwise
contraction is a complex matrix product blocked for the cache, its rows shared between
threads. Slices running in parallel keep their contractions on one thread each. Every
output adds its terms in the same order, so the result is the same bits (a test holds it
to the plain loop's, and the pinned digests hold).
- **Measured:** large products went from 2.0 to about 25 billion complex multiply-adds a
second on a 16-core desktop processor (1024 × 1024 × 1024: 0.52 s → 0.043 s).
- **Faster state-vector kernels** (`quantum_sv`). Gates on two to five qubits run at
compile-time sizes, several groups side by side, with groups of low strides gathered.
Every output is the same sum in the same order, so the state is the same bits.
- Measured in cache on one core of a desktop machine: two-qubit gates 280–345 → 400–460
million amplitudes a second, and dense three- and four-qubit gates 10–25% faster.
- On a 16-core desktop processor, dense three- to five-qubit gates are 35–50% faster.
- **`quantum_sv_metal`** (new feature, macOS): the dense state vector in single precision
on the platform's GPU, in one buffer the GPU and the host share. Half the bytes of the
double-precision state, so one more qubit fits.
- Gates are fused as `quantum_sv` fuses them, then grouped into tile passes. Each
threadgroup copies 4096 amplitudes into threadgroup memory, applies every gate of the
pass there between barriers, and copies them back.
- A kernel is built per group size, so each group stays in registers. The kernels are
compiled with the platform's safe math mode. Norms, expectations and samples are summed
on the host in double precision.
- **Checked:**
- Against the double-precision state vector on random circuits of 6 to 21 qubits at
every fusion width: amplitudes within 2·10⁻⁶, expectations within 10⁻⁵.
- Tiled and untiled runs are the same bits, and repeated runs are the same bits.
- **Measured** on a desktop machine's GPU, with the machine heavily loaded by other work:
a 30-qubit, depth-20 brickwork of 910 gates runs in about 4.2 s at fusion width 3 with
tiles, against about 8 s gate by gate.
- **The service** offers it as `statevector_single`, accuracy class `single`, where a GPU
is present. `auto` tries it after the double-precision `statevector`, so it takes the
circuits that one cannot hold: up to 33 qubits within a job's memory.
- **Measured** through the public service, on a host under heavy load:
- a 33-qubit job (a 64 GiB state, 97 gates, chosen by `auto`) in 70 s;
- 31 qubits in 23.5 s;
- 26 qubits in 2.1 s, against 10.1 s for the double-precision method. The expectation
values agree to 10⁻⁸.
- **`quantum_tableau::frame_counts` and `frame_expectations`.** Clifford circuits under Pauli
noise (`FrameStep`: depolarizing on one or two qubits, dephasing; readout flips), on up
to 32768 qubits.
- One noiseless outcome from the tableau is the reference. Each shot carries a Pauli
frame, the difference its noise makes, 64 shots to a machine word.
- Frames start with random Z bits. They leave `|0…0⟩` unchanged, and carried through the
circuit they spread each shot uniformly over the noiseless outcomes.
- Errors are placed by geometric gaps, with the crate's own logarithm.
- An expectation value is the noiseless value times the frames' mean sign against the
string, with its standard error.
- Both readouts are the same bits at any thread count.
- **Checked:** on random Clifford circuits of 3 to 5 qubits with every channel, the counts
(200,000 shots, with readout flips) and the expectation values agree with the exact
density matrix within five standard errors. Two mutations are caught: dropping one qubit
of the two-qubit channel, and dropping the random Z bits.
- **Measured** on a 16-core desktop processor: 100,000 full-width shots take 44 ms at 1,000
qubits with 10,000 noisy gates, and 1.6 s at 10,000 qubits with 100,000.
- **The service's `stabilizer` method takes Pauli noise.** Each QASM gate gets its noise on
the qubits it touched, as the other noisy methods apply it. Expectations are estimated
from at least 10,000 shots unless `trajectories` says otherwise. Amplitude damping is not
a Pauli channel and is refused. With noise, `auto` tries `density_matrix` (12 qubits,
exact), then `stabilizer`, then `trajectories`.
- **The OpenQASM writer keeps every gate** (`quantum_toolchain::to_qasm`). A gate with
three or more controls, such as Grover's oracle and diffusion past three qubits, was
written as a comment, so the program written out was not the circuit. Every controlled
gate without a standard name is now written in OpenQASM 3's modifier form,
`ctrl(n) @ g`, and `from_qasm` reads `ctrl`, `ctrl(n)`, `negctrl` and `negctrl(n)` back.
- **Checked:** each base gate under zero to five controls is read back op for op, and
Grover's search on five and seven qubits keeps its state.
- **The service** (`wai-quantum-service`, not published):
- It reads the control modifiers on `x y z h s sdg t tdg p` and on `cx cy cz ch cp ccx
ccz`. A gate with a standard name is read as that gate, so a Clifford program keeps
the stabilizer method. A phase on m qubits is written exactly with 2^m − 1 `cx` and
as many `p`, its angles dyadic when the phase is. **Checked:** on a 6-qubit state,
every amplitude against the gate applied directly where the controls hold, within
10⁻¹², each test failing when the expansion's signs or its releases are broken.
- Receipts sign the RFC 8785 canonical text of the request and the result, so a result
verifies after any JSON reader has carried it, a browser's included. Its numbers are
checked against a JavaScript engine on RFC 8785's appendix B and on 2 million random
doubles. Receipts made before it still verify.
- It reads numbers correctly rounded (serde_json's `float_roundtrip`). Before, a result
could change in its last bit on its way from the job's process to the reply.
- A job carries its request as submitted, so whoever holds its id can check its receipt.
### `wai.image.jpegai`'s hyper decoders, context stages and synthesis through the integer operator set
- **§13.6.2 is normative.** [integer-operators](extensions/integer-operators.md) §13.6.2
writes the topology of the capability's other integer networks as four `WHG1` graphs,
lowered by `jpegai-int/1` from the bundle's eight weight files in §13.0's canonical
form: `rec_y` and `rec_uv` (each plane's hyper decoder, and for luma its four context
stages, from the hyper-latent and the dequantised residual to the reconstructed
latent) and `syn_y` and `syn_uv` (the synthesis). §1's claim widens to what these
networks give for their inputs. The independent checker (`tools/wai_ops_check.py`),
written from the text alone, reproduces every lowered graph and certificate byte for
byte.
- **Ranges.** Every activation an operator can grow, and every input but the
hyper-latent, is checked within i32 (`A32`), so every sum is bounded by the weights.
On the test codestream inputs stay within 21 bits, activations within 24, and the
largest sum is 37 bits against certified bounds of 42 to 60: `acc64` holds for every
sum, and no wider accumulator is needed (§13.6.2, *Accumulators, measured*).
- **Profiles.** The `ops` member is `{ wai-ops/1, acc64, full, jpegai-int/1,
role_profiles: { hs_y: acc32, hs_uv: acc32 } }`: the set meets `acc64`, and its scale
path still `acc32` (§11's new `role_profiles`; both certifiers take
`--role-profile`). The certificate of `jpegai/v1` (`3e243dc1…`) holds the six graphs.
- **The switch.** `jpegai_decode::{decode, decode_with_stages}` run the hyper decoders,
context stages and synthesis through the operator set (`jpegai_ops::Networks`,
`jpegai_decode::load_networks`); `decode_loaded` takes the scale path and the networks
loaded once, with the eICCI net. Every committed and pinned output is unchanged: the
`Synthesis` stage hash and every other stage hash of `tests/jpegai_cross_platform.rs`,
and the decoded RGB SHA-256 that `wai-jpegai-e2e.yml` pins. The direct networks run
only in `jpegai_decode::legacy`, test-only (`legacy_int`). The neural sink lowers and
loads the networks from the members' bytes through the store of loaded sets.
- **No loader panics.** `Weights::from_json`, `IntNet::from_json` and
`IcciNet::from_json` return a `Result` (`jpegai_synthesis::BadMember`); a mistyped
member is refused `model_mismatch` where it panicked, and the sink reports that
refusal rather than decoding through the float path.
- **Planned breaks** (integer-operators §15.3 item 4, SPEC §8): a weight file that
breaks §13.6.2's member rules or shapes, or networks whose certificate does not meet
`acc64` (`model_mismatch`); a dequantised residual, a latent or an activation of the
networks past i32 as a Q16 value (`field_range`), where the decoder computed on. No
committed bundle or codestream reaches either.
- **CI.** `tests/jpegai_nets_ops.rs` runs in both fast-kernel steps of
`wai-conformance.yml` on the committed synthetic networks, and in `wai-jpegai-e2e.yml`
on `jpegai/v1`, each pinned case required by name; the differential gains the
networks' half, per push and nightly.
### wai-quantum 0.6.3: the wide stabilizer tableau, matrix product states at any angle, and fewer passes for the state vector, noisy shots and sparse Pauli sums
Every pinned digest is unchanged, and the state vector and the sparse-Pauli engine give the
same bits as before.
- **`quantum_tableau`** (new feature, in `full`): Clifford circuits on up to 32768 qubits.
The Aaronson–Gottesman tableau is packed qubit by qubit, with 32-bit qubit indices, so a
gate costs a few word operations per 64 rows.
- **Outcomes:** `Tableau::support` computes the affine subspace the computational-basis
outcomes are uniform over, by Gaussian elimination on the stabilizers with their signs
tracked. A `Sampler` then draws each shot as a random combination of its basis,
projected onto the measured qubits.
- **Expectation values:** `Tableau::expectation` gives the exact ±1/0 of a Pauli string,
from the destabilizers.
- **`quantum_mpsim`** (new feature, in `full`): matrix-product-state simulation of circuits
with any angles, on up to 4096 qubits.
- The chain is kept in mixed-canonical form, the center moved by QR. A gate on distant
qubits is routed by swaps.
- A two-qubit gate is a two-site update. It keeps the leading eigenvectors `U` of `M·M†`
and makes `U†·M` the new center, so no singular value is divided by. A matrix taller
than wide is first factored `M = Q·R`. The update is truncated to the bond dimension and
a cutoff.
- The eigensolver is new in `linalg`: Householder reflections to a real tridiagonal form,
then QL with implicit shifts, in a fixed order with only `+ − × ÷ √`. Shares below 10⁻¹²
of an update's weight are its rounding and are always dropped. An update that loses
under 10⁻¹³ of its weight is not counted as a truncation.
- Every truncation is reported: the bonds used, the summed discarded weight, and the
fidelity estimate `Π(1 − εᵢ)`.
- Readouts: amplitudes, Pauli expectation values by transfer matrices, and shots by
sequential conditional sampling.
- **Measured:**
- With nothing truncated, every amplitude and expectation agrees with the dense state
vector to 1e-10, and sampled frequencies match the probabilities. Under truncation,
the estimate tracks the true fidelity.
- On a 16-core desktop processor, on nearest-neighbour brickworks, against the crate's
one-sided Jacobi split: 500 qubits at depth 10 take 0.75 s against 3.1 s, and depth
20 at bond 64 takes 8.1 s against 35.9 s.
- At depth 20 the estimate reports what truncation cost: 0.0426 at bond 64 and 0.4712
at bond 128, the same under both splits.
- **Measured** on a random Clifford circuit of 10n gates at 10,000 qubits: the gates in
49 ms, the outcome subspace in 353 ms, 1000 full-width shots in 154 ms, one expectation
value in 27 ms. Over 40 random circuits on up to 10 qubits, the outcome set and its
uniform probabilities match the dense state vector's, as do 800 Pauli expectation values.
- **Tiled state-vector passes** (`quantum_sv`). `StateVector::run` now groups consecutive
gates into passes, as a `Schedule` sets (`run_scheduled`; `Schedule::UNTILED` gives a pass
per gate). Each tile holds the amplitudes that agree on every bit outside the pass's local
bits. A tile is copied into a buffer the size of a core's cache, every gate of the pass is
applied to it there, and it is copied back.
- A pass's dense gates act on its local bits. Diagonal gates may act on any bit.
- Gates keep their order, and each amplitude goes through the same expressions as before,
so the schedule never changes a bit. A test holds tiled and untiled states equal bit for
bit at several tile and run sizes, at one thread and many.
- A gate of three or more qubits on the top qubits used to fall back to one thread; it now
runs in tiles. Gates on two or more qubits whose lowest stride allows it update eight
groups side by side.
- **Measured** on a 16-core desktop processor, 26 qubits, double precision:
- a depth-20 brickwork of random rotations and fSim gates: 5.2 s → 2.3 s at fusion width 2;
- a QFT: 2.9 s → 1.6 s at width 3;
- a QAOA circuit: 2.9 s → 0.95 s.
- **Branching trajectories** (`quantum_noisy::branching_trajectories`). The shots of a noisy
circuit have the law of independent trajectories, but shots that took the same errors
share one state.
- At each noise event a group splits as independent shots would. The number that err is
binomial (drawn by geometric gaps, so the cost is the number of errors, not of shots),
and each erring shot chooses its Pauli uniformly. A damping event's jumps are binomial
in the group's own jump probability. The error-free prefix and the error-free majority
therefore run once.
- The largest branch carries on in place. Other branches run first in copies while fewer
than a stated number of states are alive; past that, a branch waits and is replayed.
Estimates, counts and histories depend on neither the thread count nor the state limit.
- **Checked:** expectations land within four standard errors of the exact density matrix
for three seeds, and counts with readout flips follow the exact distribution.
- **Measured**, 18 qubits and 300 shots of a brickwork circuit, against independent
trajectories: 28 times faster at two-qubit error 10⁻³, 5.6 times at 5·10⁻³, and 1.9
times at 2·10⁻² with damping.
- **The sparse-Pauli engine on a worker pool** (`quantum_spd`). A large sum is updated by
workers started once per run, each taking pieces of the table as it frees up.
- A run of consecutive Clifford gates is applied in one pass, each term through the gates
in order.
- A rotation's new terms have distinct strings, so each existing coefficient receives at
most one addition. The rest are appended in table order, and dropped magnitudes are
summed in table order.
- The thread count therefore changes the time and never a bit: a test compares one, two,
three and eight threads bit for bit. `expectation_threaded` takes an explicit count.
- **Measured:** the 127-qubit kicked-Ising run (20 steps, threshold 10⁻⁴, 2.2 million terms
at its peak) takes 9.4 s, against 49 s on one thread, with the same value.
- **The tensor-engine member's kernel compiles for a device.**
- **The kernel compiled for the simulator only.** The helper
(`quantum_fabric/tensor_engine_helper.py`) defined its kernel inside `main()` and
imported the kernel toolchain there. The simulator runs the kernel as Python, so it found
the names. Compiling for a device traces the kernel's source and looks its names up in
the kernel's module, so it found none and refused the first request (`failed to resolve
name`). On a device every test of the member failed at that request. The kernel now sits
at module level. `main()` still imports the toolchain after the target is chosen, but
binds it as module globals.
- **The compiler is found beside the interpreter.** Compiling for a device runs the
toolchain's compiler from `PATH`, and the interpreter the member is given need not be on
it. The helper now puts that interpreter's directory first on `PATH` for a device target.
- **Checked** off the device with the toolchain's own compiler: the kernel now compiles
for the device target. Execution there needs the device's runtime. The simulator path is
unchanged.
### wai-quantum-service 0.1.0: the quantum stack as a public job service
- **What it is** (`wai/wai-quantum-service`): an HTTP job service over wai-quantum. A job is
an OpenQASM 3 or 2 program and the outputs wanted: measurement counts, Pauli expectation
values, or amplitudes of named bit strings, optionally under a noise model. The service
answers with the method that ran, its accuracy, and a receipt. No account and no key.
The public instance is at `https://wai.transaction.science/quantum-jobs/v1`.
- **The OpenQASM front end** (`src/qasm.rs`) reads:
- registers, and broadcast over them;
- user `gate` definitions, inlined with their parameters;
- angle expressions;
- the standard gate libraries;
- every terminal-measurement form.
Mid-circuit measurement, classical control and gate modifiers are refused, with their
line. It lowers one circuit three ways, each exact up to a global phase: to matrix gates
(the dense state vector, tensor networks, noise), to Pauli rotations (sparse Pauli
dynamics) and to the dyadic gate set (the bit-exact state vector, the stabilizer tableau).
The tests hold each lowering to the others on every gate.
- **Seven methods, and `auto`:**
- `exact` (bit-exact, dyadic angles, 26 qubits);
- `statevector` (exact, any angles, up to the memory a job has: 32 qubits);
- `stabilizer` (Clifford, 32768 qubits, on the wide tableau below: counts and exact
expectation values);
- `pauli` (bounded, 1024 qubits, each value with its truncation bound);
- `mps` (any angles, 4096 qubits, at bond dimension up to 256: exact when nothing is
truncated, otherwise heuristic with its fidelity estimate and a warning below 0.9);
- `tensor_network` (exact amplitudes, with the contraction's cost);
- `density_matrix` (exact noise, 12 qubits);
- `trajectories` (noise as an estimate with standard errors).
`statevector` runs in tiled passes. `trajectories` runs every shot as a branching
trajectory and reports how many distinct error `histories` ran. `pauli` reports a
`ladder`: each value and bound at up to three thresholds a decade above the one asked
for (`"ladder": false` turns it off). `auto` picks the first that takes the job, and the plan says why, and why not the others.
- **A public service on a shared host:**
- every job is planned before it is queued, and one whose memory estimate is over the
per-job cap is refused at once;
- jobs run inside a total memory budget, and a large one waits until the host's free memory
covers it and a reserve;
- each job runs in its own child process, at a lower priority than the host's other work,
watched for its resident memory and its time, and stopped past either;
- clients get per-client active-job and submission limits, against a bounded queue.
The limits are stated at `GET /v1`.
- **Receipts:** every finished result is sealed with the service's Ed25519 key over the
BLAKE3 digest of the request and the result; `POST /v1/verify` checks one.
- **Measured:** 29 tests on Linux and macOS, and `smoke.sh` against local instances and the
public one. That covers a 40-qubit GHZ by the stabilizer, receipts that verify while a
changed result does not, a 100-qubit circuit's expectation values by sparse Pauli dynamics
with their bounds and ladder, 2000 noisy shots of a 14-qubit circuit by branching
trajectories, refusals with reasons, the per-client limit, and a job stopped when it
outgrew its memory. CI: `wai-quantum-service` (Linux and the macOS runner).
### `WIV1` and `WIR1` through the integer operator set's stage operators
- **`ops` members with no graph.** `wai.video.int_motion` and
`wai.{image,video,audio,splat}.int_refine` decode through the stage operators of
[integer-operators](extensions/integer-operators.md) §13.5 alone, and register
`{ wai-ops/1, stage: rans64 t4097/lenient/canonical }`: with no parameter set, the
member has no profile, subset or lowering (§11; the registry schema now requires
`set`, those three together, and a lowering or a stage).
- **The stage operators the drivers need.** `wai-ops`' E1 decodes a symbol against a
table in whatever width a payload's parser keeps it (`Rans::symbol_in`, and
`table_ok` reads its entries likewise), and E10, E11 and E12 gain per-element forms
(`mc_predict_at`, `up2_at`, `rate_at`) that read a frame, a picture or source samples
in the driver's layout, so a decode forms no copy.
- **The switch.** `int_video::{decode_typed, decode_pframes_typed}` decode `WIV1`
through E1, E10 and E9 (`int_video::ops`), and `staged::wir1::apply_unit` and
`Wir1::check_streams` decode `WIR1` through E1, E11 or E12, and E9
(`staged::wir1::ops`). Every committed output is unchanged: the `int_video` clips
(the half-pel real footage among them) and their SHA-256 sidecars, the payload
corpus, every stage of the staged corpus and the byte-equality goldens. The direct
decodes are `int_video::legacy` and `staged::wir1::legacy`, test-only (`legacy_int`).
No input decodes or refuses differently ([integer-operators](extensions/integer-operators.md)
§15.3 item 5).
- **The 32-bit form, behind a feature.** `wai-ops`' E9 32-bit form
(`stage::residual_step_i32`, `i32_form_fits`, and `residual_step_by_i32` for `i64`
operands) is behind the new `i32_forms` feature, the arithmetic a 32-bit fabric runs;
`wai-rs`' `i32_forms` decodes `WIV1` and `WIR1` through it
(`int_video::ops::decode_typed_i32`, `staged::wir1::ops::apply_unit_i32`), held to the
exact decode by the parity tests, the differential and the byte-equality goldens.
- **Text.** [integer-operators](extensions/integer-operators.md) §5E writes E10, E11 and
E12 out, §13.5 the `WIV1` and `WIR1` drivers, and the status note names the stage-only
capabilities; integer-payloads §3 and §12 and staged-delivery A.4 cite the stage
operators; SPEC §8 lists no planned break for them.
### `wai.image.jpegai`'s scale path and entropy decodes through the integer operator set
- **The residual-variance bands start where the reference decoder's tables change.**
The exact decoder put a block whose `likely` was exactly 1003, 1352 or 1937 in the
next band, for its σ addition and for its dequantisation multiplier. The reference
decoder's own tables (4352 entries per branch, indexed by `likely`) change value at
1004, 1353 and 1938, and the reference sink's float decode path, which reads those
tables, already followed them. The edges are now 1004, 1353 and 1938, in the decoder
and in [integer-operators](extensions/integer-operators.md) §13.6.1 step 3 (§15.3
item 4). `tests/jpegai_mans_golden.rs` holds both branches' additions and multipliers
to the reference's tables at every index. No committed codestream has a block on an
edge, so no output changes.
- **The lowering, from the bundle the decoder holds.** `wai-ops` gains
`lower::parse_jpegai`, `lower::jpegai_net` and `lower::jpegai_net_loaded`
(`jpegai-int/1`, [integer-operators](extensions/integer-operators.md) §13.6.1, of a
bundle already parsed; the last loads both scale graphs without writing and parsing
them again), `lower::jpegai::scale` (one plane's graph) and
`stage::mans::decode_sgm_tables` (E8 with its tables built once). The committed graphs
and certificates are unchanged.
- **The switch.** `jpegai_decode::{decode, decode_with_stages}` run the scale path and
the two entropy decodes through the operator set (`jpegai_ops`): the bundle's scale
graphs loaded under `acc32` with its tables (`jpegai_decode::load`, a
`jpegai_ops::ScalePath`), then each plane's hyper-latent, scale, mask, `likely`, RVS
addition, `σ_idx` and latent. Every committed and pinned output is unchanged: the
decoded RGB of `jpegai/v1`, every stage hash of `tests/jpegai_cross_platform.rs`, and
the reference decoder's symbols and scales. Each now returns a `Result`
(`jpegai_decode::Refusal`); `decode_loaded` decodes with a scale path loaded once. The
direct decode is `jpegai_decode::legacy` and `jpegai_ops::legacy`, test-only
(`legacy_int`). `NormTables`' bounds and CDF rows are `i64`, so a load check sees the
value the file gives. The float decode path decodes its scale path and entropy through
the same driver, from its own bundle's members.
- **`ops` member.** `wai.image.jpegai` registers `{ wai-ops/1, acc32, full,
jpegai-int/1 }` (SPEC §5, §7).
- **No silent fallback.** The neural sink lowers and loads the scale path from the
members' bytes through the store of loaded sets (keyed by the listing of `norm.json`
and `weights/hs_{y,uv}.json`). A refusal of the exact decode is the decode's result
(`model_mismatch`, `stream_exhausted`, or `set_incomplete` for a member the set
lacks); only a sink without the bundle, or a thread or target that cannot run the
float stages, decodes through the float path ([determinism-tiers](extensions/determinism-tiers.md) §5).
- **The browser crate.** `decodeJpegAiMans` decodes through E8 and throws on a refusal;
`decodeJpegAiFull` reports the decode's refusal.
- **One implementation.** The float path's own ports of the mANS decoder and of the
hyper-scale network (`neural::jpegai_mans`, `neural::jpegai_hyper`) are deleted: that
path runs the shared driver. The exact decoder's direct pieces (the crate-root mANS
port, `jpegai_synthesis::decode_hyper_scale`, and the pipeline's `skip_mask`,
`rvs_additive`, `rvs_likely` and `sigma_index`) are test-only (`legacy_int`), the
reference the differential (`tests/int_ops_differential.rs`) and the refusal-parity
tests hold the operator set to.
- **Planned breaks** (integer-operators §15.3 item 4, SPEC §8): a bundle that fails a
load check of §13.6.1 (a bound above 255 or a shift outside `0..=127`, which the loader
wrapped, among them) or whose scale graphs do not meet `acc32` (a sum past `i32`, a
shift past 63) is refused (`model_mismatch`); a substream that ends before the decode
it carries is refused (`stream_exhausted`) where the decoder panicked. No committed
bundle or codestream is affected.
### `wai.text.timed`: timed text and live captions
- **The extension** ([timed-text](extensions/timed-text.md)) registers the record
`wai.text.timed`. A cue is a span of media time and its text in a closed set of styling,
italic runs, line breaks and six anchors, which WebVTT and the IMSC1 Text Profile both
express; text outside the subset both hold exactly (a tab, two spaces in a row, a space at
a line's edge) is refused rather than changed.
- **Live captions.** A caption is a `caption` event of `wai.live.events`, unchanged. A
machine caption is revised in place: each revision is a new `caption` event, followed by
a `meta` event carrying the `WTX1` annotation, which supersedes the revision before it by
digest, says whether the text is final and how much is stable, and carries the anchor and
italics. A verifier checks that revisions form one line per cue, keep their language,
anchor and stable prefix, and supersede only a partial of their own principal; it states
what a sink shows at an instant and the cues a stream resolves to. A publisher repeats
the open partial and each caption's annotation at a group's start, so a subscriber that
joins mid-stream shows what one from the start shows. live-events needs no change.
- **Mappings.** WebVTT documents and segments with `X-TIMESTAMP-MAP`, and a reader of the
subset; IMSC1 documents, refused when a sound check of the hypothetical render model does
not pass; the WebVTT and IMSC1 (cue mode) MoQ caption timelines, whose first object of
each group repeats every cue still showing, and their catalog entries; CTA-608 on field 1,
CC1, pop-on and roll-up with windows of 2, 3 and 4 rows, odd parity, doubled codes, the
special and extended characters with their fallbacks, and the slot clock's rate as a
parameter; CTA-708 is specified only.
- **The disclosure label.** Origin and model digest travel through every target: the HLS
`public.machine-generated` characteristic (`draft-pantos-hls-rfc8216bis-22` §4.4.6.1),
C2PA 2.4 `c2pa.ai-disclosure` and the IPTC digital source type, a WebVTT class span, IMSC1
attributes, timeline members, a catalog field, and a sidecar published by
`EXT-X-SESSION-DATA` for targets that cannot carry it, such as 608.
- **Player obligations** for captions under the viewer's control, persistent and
discoverable, each citing EN 301 549 V4.1.1 or WCAG 2.2.
- **Reference and corpus.** `wai-rs` feature `timed_text` (`src/timed_text.rs`, with
`cc608.rs`); `live-conformance/captions/`, written from fixed seeds by
`wai_timed_text_vectors` with every expected `.vtt`, `.ttml` and `.608.hex`, and checked
by `tests/timed_text.rs` and by `live-conformance/timed_text_verify.py`, a second
implementation that shares no code with `wai-rs`, parses every IMSC1 document with an XML
parser, and requires every mutated copy of the corpus to fail. The registry lists the
record, the magic `WTX1`, its catalog fields and its session data id.
- **Not checked against the standard.** CTA-608-E and CTA-708-E could not be read for this
revision. The 608 tables are as 47 CFR 79.101 reproduces them; the extended characters are
not there, and the extension marks its table of them for checking.
### `wai.live.recording`: a room session as one package, and clips that prove what they are
- **The extension** ([live-recording](extensions/live-recording.md)) registers the record
`wai.live.recording`. A finished room session is one WAI package: a directory of files and
its `LISTING` (SPEC §3.1), as a packaging package is, whose components are the room (its
descriptor, grants and key-log head), its events (each principal's chain whole, the form
live-events §8.6 gives a recording), one component for each participant's take (its `WTM1`
manifest, the chunks the store holds, its receipts and consents) and, optionally, the
composite by its composition receipts. Each component is named as SPEC §7.1 names one, by
an `id` and a `sha256`, here the SHA-256 of the listing of its files.
- **The recording manifest** (`WRM1`) is signed by the room authority, or by a principal that
held the record power at every instant of the session span (its grant in effect from the
span's start, with no leave, exchange or expiry taking the power away before its end; a
refusal states the first instant without it), and names every component with the span on
the room clock and the region the package is kept in, held to the room's region pin. Every
take the package holds lies within the span; the package holds the room's chains whole. A
recording verifier replays the room and each take and gives `complete` (every take the
room's events open is held, each one `complete` with its commitments unbroken from open to
close, and no event missing) and `publishable` for each purpose.
- **Clips.** A clip record (`WCL1`) covers a span of one take, by chunks or by a byte range
of the master (the boundary chunks carried whole, with trims), or one window of the room
clock across several takes. Each part carries its chunks' descriptors and one range proof
against a commitment the device made while it captured, the closing one or, while the take
runs, the one that covers the span. A clip verifier needs the clip's bytes, the record, the
take's commitments up to that one, the device's key and the room descriptor, never the take,
and checks `single-take` over the span. An aligned clip states each part's window on its
device's clock and its bound: the jitter to the room clock, twice it between two takes.
- **Range proofs.** The output tree gains `range_proof` and `verify_range`: RFC 6962's audit
path taken over a run of leaves, whose one-leaf case is the audit path node for node and
whose check agrees with RFC 9162 §2.1.3.2's, tested over every run of trees of up to 33
leaves. The take tree reuses it; no new tree.
- **Mappings.** The HLS and LL-CMAF presentation of a CMAF master (one Media Playlist per
take, its header by `EXT-X-MAP` byte range, parts proved with their chunk) and the MoQ
addressing of a recorded take's chunks for a FETCH (a group per chunk, a `LOCATION_FILTER`
per clip part) are specified; the reference states each clip part's FETCH address and
writes no playlist.
- **Reference and corpus.** `wai-rs` feature `live_record` (`src/live_record.rs`);
`live-conformance/recordings/`, written from fixed seeds by `wai_live_record_vectors`, whose
performer's take is capture binding's `lesson` take byte for byte, checked by
`tests/live_record.rs` and by `live-conformance/live_record_verify.py`, a second
implementation that shares no code with `wai-rs`, checks range proofs by the extension's
recursion and by RFC 9162's own algorithm, and requires every mutated copy of the corpus to
fail. The registry lists the record and its magics (`WRM1`, `WCL1`). live-events §8.6,
live-rooms §3.7 and capture-binding §1 point at the extension.
### `wai.live.compose`: a room's composition as an integer function
- **The extension** ([live-compose](extensions/live-compose.md)) registers the record
`wai.live.compose`. A `WCD1` composition descriptor, signed by the room authority or by a
principal whose grant has the compose power, fixes a canvas (each edge even, 16 to 1920
pixels, at most 1920 × 1080 pixels, 1 to 60 frames a second), an sRGB background, the
highest layer composed, a layout, the grant attributes it reads, and its limits on tiles
and voices.
- **The participant set** at a room-clock instant is the room's lifecycle replay up to it:
every present publisher but the compositor, its video source (own video, own screen, or
its live place in a slot) and audio source, each with the dimensions of the composed
layer, the attributes of its grant in effect, and its join order. A gap in a room key's
chain is `lifecycle_incomplete`; a gap elsewhere changes nothing. The set has a digest.
- **The layout** (§5) is integer arithmetic throughout, with every rounding stated: a grid
chosen by the largest tile of the reference aspect among every column count, its rows
centred; tile aspects auto, 16:9, 1:1 and 3:4; a featured participant above the others;
fill, cover and contain, by default cover for grid tiles and contain for the featured
one; stopped video shown or omitted; order by attribute, then join order, then key; and
picture in picture, the PiP participant's window over the grid in any corner, of a set or
derived size, static or dynamic (alone, it is laid out as though it had no PiP
attribute). The layout is computed again at every lifecycle event and grant exchange.
- **The mix** (§6): per-voice gain and pan as integers in units of 1/4096, over signed
16-bit PCM, summed exactly in 64 bits and saturated once, so it is byte-exact over the
decoded PCM; a decode and a lossy re-encode are not, and the receipt binds objects.
- **Where it runs** (§7): on each sink by default, with no server encode; a server
compositor must be the descriptor's compositor, hold the compose power and sign a `WCR1`
receipt for each object of a composed track, naming the descriptor, the set's digest at
`t`, the output object and the input objects of exactly the tracks the layout reads.
- **Live rooms.** §1, §3.3 and §4.4 point at the extension for the compose power and the
attributes `featured`, `order` and `pip` (and `gain` and `pan`, which it adds).
- **Reference and corpus.** `wai-rs` feature `live_compose` (`src/live_compose.rs`; the
layout and the mixer in `src/live_compose/layout.rs` use nothing beyond `core`, `Vec` and
`String`); `live-conformance/compose/`, written from fixed seeds by
`wai_live_compose_vectors`, and checked by `tests/live_compose.rs` and by
`live-conformance/live_compose_verify.py`, a second implementation that shares no code
with `wai-rs`, steps through the room's lifecycle replay itself and requires every
mutated copy of the corpus to fail. The registry lists the record and its magics (`WCD1`,
`WCR1`).
### `wai.capture.take`: a device's master recording bound to a live room
- **The extension** ([capture-binding](extensions/capture-binding.md)) registers the
record `wai.capture.take`, for the case of a lesson or an audition where the live call is
low quality and the master is made on the performer's own device and uploaded after the
session. A take answers a `record_start` addressed to the device; its id is derived from
the room, the device and that event. The device's grant binds it to the principal it
captures for (the grant attribute `subject`), and may mark that principal a minor
(`minor`).
- **Commitments.** While it captures, the device emits a `take_commit` event every commit
interval of the room's recording policy (live-events §4.12's payload, defined here): the
take id, the chunk range it adds, the root over every chunk so far, its offset to the room
clock, and an RFC 9162 consistency proof that the root extends the last one. The take
tree is staged-measurement Appendix C's (RFC 6962's Merkle tree hash, its node) over
leaves that bind each chunk's index, capture range, length and hash; the output tree
exposes that hash and gains consistency proofs for it.
- **Upload and manifest.** The master is uploaded resumably by chunk hash; the store, a key
the room's key log lists with the new authority `capture/store`, signs `WUR1` receipts of
the upload's state machine (open, progress, resume, sealed) within the room's region pin.
The device signs a `WTM1` take manifest listing the chunks, their root, every
commitment's digest and each subject's `WCN1` consent (purposes live, record, distribute
and train; a guardian's signature for a minor; revocable through the key log).
- **Verdicts.** A take verifier replays the room (live-rooms §9) and gives `single-take`,
`aligned`, `complete` and `publishable` for each purpose a verdict, each failure a named
reason (§12): a commitment not delivered is `delivery_gap`; a part not committed at its
index is altered, reordered or extra; a missing part fails `complete`, not `single-take`.
A room-clock instant maps into the master, so a marker set on the live call lands in it.
- **Live rooms.** The recording policy gains `commit_interval_ms` and `jitter_ms`
(live-rooms §3.7; 2 000 and 500 by default), and the rooms corpus is regenerated.
- **C2PA live video** (C2PA Technical Specification 2.4 §19): the mapping of a take and its
CMAF output onto per-segment manifests and Verifiable Segment Info, session keys, AI
disclosure and sustainability is specified; the crate has no CBOR or COSE encoder, so it
implements only the stream id.
- **Reference and corpus.** `wai-rs` feature `capture_take` (`src/capture_take.rs`);
`live-conformance/takes/`, written from fixed seeds by `wai_capture_take_vectors`, and
checked by `tests/capture_take.rs` and by `live-conformance/capture_take_verify.py`, a
second implementation that shares no code with `wai-rs` and requires every mutated copy
of the corpus to fail. The registry lists the record and its magics (`WTM1`, `WUR1`,
`WCN1`).
### `wai.live.room`: room descriptors, room grants and the room verifier
- **The extension** ([live-rooms](extensions/live-rooms.md)) registers the record
`wai.live.room`. A room is fixed by a signed descriptor, `WRD1`, whose digest is the
room id every `WEV1` event names: its authority, its clock and `t0`, the roles a
principal may hold (publish per media kind, subscribe up to a layer, the event kinds it
may send, and the moderate, direct, compose and record powers), its limits and event
ceiling, the slots several publishers may stand behind, its publish profiles, its
review policy and its recording policy with a region pin.
- **Grants.** A principal takes part under a signed room grant, `WRG1`: its role and
capabilities, a window of at most fourteen days, its delivery origins, a single-use
nonce, a viewer id, the `WEP1` event policy of live-events §5 it is held to, its joule
and funds ceilings, the profiles it publishes with, and at most 1 KB of composition
attributes. A grant is exchanged for one chained to it at a tick of the room clock,
recorded as the `grant_exchange` lifecycle event, and revoked through the authority's
key log: the head's `revoked_modules` lists revoked grants and viewers, and the
authority's key holds the new authority `live/room` (jwp-receipts §7.2, §7.5, §7.6).
- **Tracks, slots and profiles.** A track is named by the MOQT namespace tuple (room,
principal or slot, media kind) and its layer. A slot is a virtual track: a publisher of
higher precedence takes it over with no new session, and a standby takes it on failover
under the slot's rule. The lifecycle kinds' `detail` is defined. The publish profiles
`speech`, `music`, `screen`, `camera-720` and `camera-1080` fix codecs, layers and
limits as data.
- **Room verification.** Given a descriptor, grants, a key-log head and every principal's
chain, a verifier merges the chains by room clock, verifies each event with the
live-events verifier under the policy of the grant in effect at its `t`, and replays the
lifecycle into room state; each refusal has a named reason (§11).
- **Bindings**, as tables: the Common Access Token's claims (CTA-5007-B), the `moqt` claim
of `draft-ietf-moq-c4m-01` (whose scopes the reference derives from each grant), URI
Signing (RFC 9246) and Privacy Pass (`draft-ietf-moq-privacy-pass-auth-03`). No CAT
encoding: the crate has no CBOR.
- **Reference and corpus.** `wai-rs` feature `live_room` (`src/live_room.rs`);
`live-conformance/rooms/`, written from fixed seeds by `wai_live_room_vectors`, and
checked by `tests/live_room.rs` and by `live-conformance/live_rooms_verify.py`, a second
implementation that shares no code with `wai-rs`. The registry lists the record and its
magics (`WRD1`, `WRG1`).
### `wai.live.events`: a signed, timeline-bound event track
- **The extension** ([live-events](extensions/live-events.md)) registers the record
`wai.live.events`. Timed metadata, ad and chapter cues (with an SCTE 35-1
`splice_info_section()` or an SCTE 35-2 message carried verbatim), chat, reactions,
polls, markers, remote-control commands, captions with their machine-generation label,
moderation decisions, take commitments and room lifecycle are each one `WEV1` record:
signed by its principal, hash-chained per principal (`seq`, `prev`), stamped with the
room clock and optionally anchored to one MoQ object. Its digest is SHA-256 over the
record without the signature, and its signature is verified strictly (SPEC §7
*Signatures*).
- **Policy and verification.** The event policy `WEP1` states the kinds a principal may
send and the payload, text and rate bounds, every bound a default a room may change; the
room grant of a later extension carries the same fields. A verifier refuses a record
with a named reason (§9), and reports a delivery gap as a gap, never as corruption.
- **Polls** replay as `wai.feed.state` cells through `wai.world.sim.feed0` (`ADD_INT`):
a tally is replay-equivalent, and a tally a poll's author states is checked.
- **Bindings**, field semantics only: an MSF event timeline over MoQ (`eventType`
`urn:wai:event:1`, one track per principal, one-minute groups, a join set repeated at
each group start), `EXT-X-DATERANGE` (`CLASS="science.transaction.wai.event"`,
`X-WAI-EVENT`, with `SCTE35-OUT`/`-IN`/`-CMD` for cues), a version 1 `emsg`
(`scheme_id_uri` `urn:wai:event:1`), a WebRTC data channel and a recording.
- **Reference and corpus.** `wai-rs` feature `live_events` (`src/live_events.rs`);
`live-conformance/events/`, written from fixed seeds by `wai_live_events_vectors`, and
checked by `tests/live_events.rs` and by `live-conformance/live_events_verify.py`, a
second implementation that shares no code with `wai-rs`. The registry lists the record
and its wire identifiers (`WEV1`, `WEP1`, `urn:wai:event:1`,
`science.transaction.wai.event`, `X-WAI-EVENT`).
### wai-quantum 0.6.2: the HIP member's matrix-instruction kernel, and the Metal member's tensor-operation kernel
Additive. Every pinned digest is unchanged.
- **Every product by the shading language's tensor operation**
(`quantum_fabric::metal::TENSOR_OPS_SOURCE`, `tensor_ops.metal`; `MetalMember::tensor_ops`):
a third Metal kernel for the gate, through the language's version 4.0 tensor operation (a
bf16 matrix multiply accumulated in f32), which the platform maps to the GPU's matrix
hardware where it has it. The operation reads and writes device memory, so each threadgroup
owns a slice of scratch (`TENSOR_OPS_SCRATCH_BYTES`): a 64-pair tile is one multiply of
64 × 32 limbs by 32 × 128 weights, in four 32-column runs. Its operands are offset-binary, as
for the HIP kernel below, so the result does not depend on how an accumulator treats a
negative term. A device or operating system without the operation
(`DeviceIdentity::has_tensor_ops`: GPU family 1007 or later, operating system 26 or later)
is `MetalError::Unsupported`. Eight mutants (`TENSOR_OPS_MUTANTS`), among them nine-bit
limbs, every run reading the first columns of the weights, and a wrong offset correction.
- **Measured** on two GPU generations, silicon: family 1009 (the macOS runner's) and family
1010, the generation whose GPU cores carry matrix accelerators. On each: 9/9, 66020 probe
pairs, all four pinned certificates, circuits to 22 qubits, 8 of 8 mutants killed. On family
1010 the integer and SIMD-group matrix kernels are bit-exact too (9/9 each, 6 of 6 and 7 of 7
mutants). With signed limbs instead, the tensor operation still agrees on all 66020 pairs on
both generations: the offset is for the instruction below, not needed here. Where the
platform runs the operation is its own choice; it is not observed here. CI: a step of
`wai-quantum-metal` on family 1009; family 1010 was run by hand, the binaries built on the
build host.
- **Every product by the bf16 matrix instruction** (`quantum_fabric::hip::WMMA_SOURCE`,
`wmma_limbs.hip`; `HipMember::wmma`): a second HIP kernel for the same gate, for the gfx11
architectures (`DeviceIdentity::has_wmma`), whose wavefronts of 32 multiply bf16 at
16 × 16 × 16 and accumulate in f32. One wavefront multiplies sixteen pairs' limbs by the
gate's limb weights in seven 16-column steps, in the instruction's own operand layout. A
device without it is `HipError::Unsupported` (new). Eight mutants (`WMMA_MUTANTS`): nine-bit
limbs, the result's rows in another instruction's layout, and a wrong offset correction
among them.
- **Offset-binary limbs, because of what the instruction does.** Its f32 accumulation is not
exact once a negative term enters a sum. Measured on gfx1151, on the instruction alone:
`1·1 + (−1)·0` returns `1 − 2^-24`, `1 − 1` returns `−2^-24`, `128 − 64` returns
`64 − 2^-17`; sums of non-negative terms come back exact. The CUDA kernel's signed top limb
therefore fails the probe on this GPU, off by one in a coefficient. So each value is taken
as `x + 2^31`, all four limbs unsigned, the instruction forms `(e + 2^31)·(a + 2^31)`
exactly, and the kernel removes the offset in 64-bit integers:
`e·a = (e + 2^31)·(a + 2^31) − 2^31·(e + a) − 2^62`, modulo `2^64`. The non-integer
results the limb program gave through one compiler's HIP build for this GPU (0.6.0) are
consistent with the same behaviour, though which instruction that build chose was not
checked.
- **Measured** on the integrated GPU (gfx1151, silicon): 9/9, 66020 probe pairs, all four
pinned certificates, circuits to 22 qubits, 8 of 8 mutants killed; the other kernel and the
state vector unchanged there. CI: a step of `wai-quantum-hip`; its absent leg runs the new
tests over no device.
### wai-quantum 0.6.1: the CUDA member's tensor-core kernel, and the Metal member
Additive. Every pinned digest is unchanged.
- **Every product on the tensor cores** (`quantum_fabric::cuda::TENSOR_SOURCE`,
`tensor_limbs.cu`; `CudaMember::tensor`): a second CUDA kernel for the same gate, for
devices of compute capability 8.0 or later (`DeviceIdentity::has_tensor_cores`). Each
32-bit value is four 8-bit limbs in bf16, the lower three unsigned and the top one
signed. One warp multiplies sixteen pairs' limbs by the gate's limb weights (sixteen
products per pair, seven coefficients each) in fourteen `m16n8k16` steps, accumulated in
f32. Every limb product is at most `2^16` and every coefficient sums at most four, so f32
holds every partial sum exactly in any order. The kernel adds the coefficients, shifted
into place, in 64-bit integers; the rest is the other kernel's arithmetic on exact
products. The member id is the device's; the implementation id is the kernel's own
(`cuda-tensor-limbs8/…`), and the member is probed before it is returned. A device below
8.0 is `CudaError::Unsupported` (new). Seven mutants (`TENSOR_MUTANTS`), one of which is
nine-bit limbs, which bf16 does not hold.
- **Measured** on the discrete GPU (compute capability 8.9, silicon): 66020 probe pairs
agree, all four pinned certificates, circuits to 22 qubits match native, 7 of 7 mutants
killed. The other kernel and the state vector are unchanged there (all four pins, 6 of 6
and 4 of 4 mutants). CI: a step of `wai-quantum-cuda`; its absent leg runs the new tests
over no device.
- **The Metal member** (`quantum_fabric_metal`, opt-in, macOS): the gate on the platform's
GPU through its own interface, compiled at run time by the operating system's compiler.
Two kernels, each with its own implementation id and mutants:
- `lanes64.metal` (`metal-lanes64/…`): the CUDA kernel's exact 64-bit integer arithmetic
in the Metal shading language, one serial dispatch per gate in one command buffer, the
state in memory the GPU and the host share;
- `matrix_limbs.metal` (`metal-matrix-limbs8/…`, `MetalMember::matrix`): every product
formed by the GPU's SIMD-group matrix multiply, from 8-bit limbs in bf16 accumulated in
f32, by the tensor-core kernel's argument. One SIMD group multiplies eight pairs' limbs
by the gate's limb weights in fourteen 8 × 8 column blocks, and each thread joins its
four products and forms one row. A device below GPU family 1007 is
`MetalError::Unsupported`.
A device is keyed by its GPU family number (`gpu-msl-<family>`), and its member id binds
the device, its registry id, family and memory, and the operating system's version. Off
macOS the member builds, finds nothing and says why.
- **Measured** on the macOS runner's GPU (family 1009, silicon): both kernels 9/9, each
with 66020 probe pairs, all four pinned certificates and circuits to 22 qubits; 6 of 6
and 7 of 7 mutants killed. Nine-bit limbs break 297 of the 484 edge pairs, so the
multiply takes bf16 inputs. CI: `wai-quantum-metal`, on that runner, and its absent leg
on Linux.
- **Measured by hand, outside CI:**
- the gate as a portable tensor program, through XLA on a cloud tensor processor: the
gate's module exact and its three mutants failing where they should, and the limb
program (`stablehlo::limb_module`) exact on the processor's matrix unit, with the
nine-bit set differing as it must;
- `wai-quantum-qpu`'s `native_turns` against the service's simulator, no hardware used:
with no noise, statistical parity at fidelity 1.0 on both gatesets' corpora (16
circuits of 5 qubits, 1000 shots each), which checks the compilation to the service's
native gates and the wire contract; with one of the service's device noise models,
fidelity 0.909 (V1) and 0.927 (V2).
### `wai.world.model`'s integer renderer through the integer operator set
- **The lowering, from the models the renderer holds.** `wai-ops` gains
`lower::world_net` and `lower::world_net_loaded`: `world/1`
([integer-operators](extensions/integer-operators.md) §13.5, informative) of two
networks already parsed, the second loading `T` before it lowers `g_s`. Both write
and refuse what `lower::world` does from the bytes; the committed sets' graphs and
certificates are unchanged.
- **No `ops` member.** The renderer runs through the operator set, but its lowering is
informative: the capability's registered payload is its signed action trace, and no
payload text yet binds a trace to a renderer's set. SPEC §5's row cites §13.5, and the
registry's test holds that `wai.world.model` has no `ops` member.
- **The direct guard, test-only.** `int_world::legacy::checked` runs the direct renderer
with the check every other integer decoder made before each layer
(`run_net_checked`); the tests hold the `check` ranges of `world/1` to it, layer by
layer, at the edge of every range and through the whole renderer.
- **The switch.** `int_world::{decode, decode_deltas, decode_planes}`, and the browser's
`WorldModel`, now render through `world/1`: `T` and `g_s` lowered and loaded under
`acc64` once (`int_world::load`, an `int_world::World`), then planned on the latent's
shape, then run frame by frame. Every committed frame and clip digest is unchanged, on
every leg and in wasm32. Each now returns a `Result`; `WorldModel`'s methods throw on a
refusal. The per-row `step`, `step_delta` and `step_planes` of a parsed model are
replaced by `World::step`, `World::step_delta`, `World::step_action` and
`World::render`. The direct renderer is `int_world::legacy`, test-only (`legacy_int`).
- **Planned breaks** (integer-operators §15.3 item 2, SPEC §8). Before the first frame,
where the direct renderer rendered from the input or panicked: a set whose `afrac` is
outside `1..=30` or unequal, with no action plane, or that breaks a `WHM1` network's
rules (`model_mismatch`); a row without one value per action plane, and `T` or `g_s`
that does not fit the latent (`model_mismatch`); a latent that is not `C·lat·lat`
values (`geometry_mismatch`), of side 0 or past `2^26` values, and a move whose plane
value leaves `i64`. A value outside a `check` range is refused (`field_range`). No
committed set or clip is affected.
- **Memory.** With its set loaded, a clip through the operator set holds no more than the
direct renderer held for it, and so do one step and one frame
(`tests/int_world_memory.rs`).
### `wai.neural.int_mlicpp` through the integer operator set
- **Its `ops` member in the registry** ([integer-operators](extensions/integer-operators.md)
§11). `registry/capabilities.json` gives `wai.neural.int_mlicpp` the member `{"set":
"wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "wmb1/1", "stage":
{"rans64": {"table": "t2", "escape": "lenient", "end": "final_only"}, "bucket":
"ceil"}}`. SPEC §5's row cites the payload and bundle
([integer-payloads](extensions/integer-payloads.md) §14) and the lowering
(integer-operators §13.4), and SPEC §7 names the capability among those that decode
through the operator set.
- **The decode.** `int_mlicpp::decode`, and so `int_payload::decode_int`, the neural sink
and every sink that decodes through them, now runs the bundle's three networks through
their lowering `wmb1/1` ([integer-operators](extensions/integer-operators.md) §13.4):
each lowered by `whm1/1` and loaded under `acc64` with its certificate, then planned on
the payload's shapes, before either stream. The stage steps are unchanged, with the
`t2` table rule and the `final_only` end rule kept: a symbol decoded after a stream ran
out is still decoded and fed to the passes after it, and the refusal comes at the end.
The committed vector decodes to the same image, with the same bucket and symbol for
every element; every committed refusal keeps its code. The direct decode is kept,
test-only, behind the `legacy_int` feature. The store of loaded sets keeps the three
loaded networks under the SHA-256 of `model.wmb`.
- **Planned breaks**
([integer-operators](extensions/integer-operators.md) §15.3 item 3, SPEC §8). A bundle
is refused before either stream (`model_mismatch`) for the rules of the other `WHM1`
decoders: a bias of magnitude `2^63 − 1` or more, a `check` range its producer cannot
meet, a network with no layer of tag 0, 1 or 2, a header `shift` or `gf` of 128 or more
that no layer uses. Four checks the decoder made only when a network ran now come
before the streams with the same code: a layer that does not fit the channels before
it, a network that does not read the payload's channel count, a header `shift` or `gf`
of 128 or more that a layer uses, and a leaky ReLU's shift of 64 or more. A convolution
whose padded side exceeds `2^26` is refused.
- **The direct guard is test-only.** With this decode on its lowering, no production
decode calls `int_hyper_synth::run_net_checked`, its per-layer checks or its IGDN check:
the `check` ranges of every lowered network make them. They move into the test-only
`int_hyper_synth::legacy` (feature `legacy_int`), beside the direct decodes that call
them for the tests that hold the two to each other. `run_net` and `decode`, the
unchecked path the integer world renderer uses, stay.
- **The refusal codes, written down.**
[integer-payloads](extensions/integer-payloads.md) §14 said that every refusal of this
capability is `payload_malformed`. The decoder reports `model_mismatch` for a bundle
that breaks its rules or does not fit the payload, as it did before this revision and
as its tests pin; §14 and integer-operators §4 now say so.
### Load step 8 in closed form
- **The bounds pass.** `wai_ops::bounds` now computes each `conv`'s and `conv_transpose`'s term hull and partial-sum interval ([integer-operators](extensions/integer-operators.md) §8.2) from a few sums per output channel, or per residue for `conv_transpose`:
- the least and greatest weight;
- the sums of the positive and of the negative weights. These are kept in `i64` where the tap count times the largest magnitude shows that every partial sum fits, and in `i128` otherwise.
An `igdn` row now folds in `i128`. These forms give the rule's integers wherever no operation of its fold can saturate. The pass checks that condition: both input ends in `i128` with `l ≤ h`, the result within `±2^255`, and no `i128` overflow. Anywhere else it folds term by term in 512 bits, as before. §8.2 gains an informative note on the closed form. No certificate, verdict, refusal or output changes.
- **The oracle.** The pass as it stood is kept as `wai_ops::bounds_oracle`, test-only; another crate's tests reach it through the `oracle` feature. The two passes must agree node for node, with the same refusals and the same first failures, on:
- every committed lowered graph and graph vector;
- the pretrained conditional-codec set, when `WAI_SOTA_INT_DIR` names it;
- random operators over input intervals the pass never forms (the ends of `i64` and `i128`, up to `2^255`, and `l > h`);
- random graphs that reach saturation;
- every graph the integer-operator differential's generators lower (`tests/int_ops_differential.rs`).
- **The lowering and the load.**
- `Builder::finish` now returns the graph it parsed back from its bytes.
- `whm1_net_loaded` and `wam1_net_loaded` (new) load that graph without writing and parsing its bytes a second time (`graph::load_lowered`, crate-only). They give `load`'s result, refusal for refusal. The `int_hyper`, splat and audio drivers use them.
- The `WHM1` and `WAM1` lowerings now move each layer's lists into the graph instead of copying them.
- The `WHG1` writer now adds a list's bytes at once and stores each value as one array.
- The pass reads a node's weight range once.
- **SHA-256 on 64-bit Arm.** `wai-rs` now enables sha2's `asm` feature on `aarch64` targets other than Windows. The processor's SHA-2 instructions are used where it has them, detected at run time. The digests are the same, and the wasm32 build is byte-identical. The lockfiles of the workspaces that build `wai-rs` now include `sha2-asm`.
- **Cost.** For the pretrained conditional-codec set, lowering and loading both networks is now a small part of one decode, natively and in wasm32. A decode that loads its set now costs about what a decode that finds it held costs. The browser demo is one such decode: it decodes once per view.
### wai-quantum 0.6.0: the HIP, npu-cores, tensor-engine and avx512 members, the dataflow device's vector engine, and the gate as a portable tensor program
Breaking in three places (the last bullet). The default features, `full`, and every pinned
digest are unchanged.
- **The HIP member** (`quantum_fabric_hip`, opt-in, Linux): the CUDA member's kernel text,
unchanged, compiled at run time by HIP's runtime compiler for each device's own
architecture, with contraction off. The runtime and its compiler are loaded when first
used; the device's properties are read in the runtime's frozen layout, or the device is
refused. Its own implementation id (`hip-lanes64/…`), its own key (`gpu-hip-<arch>`); the
mutants are the CUDA kernel's. With `quantum_sv`, the dense f64 state vector runs the CUDA
kernels' text too (`hip-sv64/…`).
- **The npu-cores member** (`quantum_fabric_npu_cores`, opt-in, Linux): the gate's
arithmetic on one compute core of a neural processor's tile array, programmed directly
rather than through a model runtime. The core forms each gathered pair's four rows in
exact 64-bit integers; the host checks their range and keeps the low 32 bits. The program
(`npu_cores.cc` in `npu_cores_design.py`) is built at run time with the tile array's open
toolchain and cached per variant, generation and toolchain; the host side
(`npu_cores_host.cpp`) is compiled against the device runtime and loaded. One program
serves every gate and the arithmetic probe; each mutant is its own program. Key
`npu-cores-<pci vendor>-<device>`.
- **The tensor-engine member** (`quantum_fabric_tensor_engine`, opt-in): the gate's sixteen
products per pair as exact limb convolutions in one bf16 matrix multiply per tile of pairs,
accumulated in fp32, on a tensor processor's matrix engine. Each value is four 8-bit limbs,
which bf16 holds exactly; every limb product is at most `2^16` and every coefficient a sum of
at most four, so fp32 holds every partial sum exactly in any order, on any engine that
multiplies bf16 and accumulates in fp32. The host checks each coefficient is an integer
below `2^24`, assembles the products and does the rest. The kernel runs as a helper under
the engine's kernel toolchain; its target here is that toolchain's simulator, and every
certificate says so. One mutant is nine-bit limbs, which the engine's inputs cannot hold.
- **The avx512 vector unit** (`quantum_fabric_vector`): eight 64-bit lanes, each `fx` an
exact 32×32 multiply, an add and the unit's own 64-bit arithmetic shift. Discovery opens
one member per instruction set the processor reports, so a processor with both units has
both members.
- **Measured** on an integrated GPU (gfx1151), a neural processor's tile array and the
processor beside them, all silicon: the HIP member 9/9 (66020 probe pairs, all four pinned
certificates, circuits to 22 qubits, 6 of 6 mutants) and its state vector 3/3 (the pin
`b7d129d0…`, 4 of 4 mutants); the npu-cores member 9/9 (66020 probe pairs, all four
certificates, circuits to 20 qubits, 3 of 3 mutants); avx2 and avx512 9/9 each; the
tensor-engine member on its simulator 9/9 (66020 probe pairs, all four certificates, 4 of 4
mutants); and,
unchanged, the WebGPU member (`gpu-vulkan-1002`) and the OpenCL member
(`opencl-gpu-1002`, the state vector's pin included) bit-exact there. CI runs each of them
on that host.
- **The vector engine as a member** (`quantum_fabric_dataflow`, key `dataflow-1e52-b140-vector`):
on the device generation whose vector engine multiplies 23-bit numbers, the gate's
arithmetic runs on that engine, 32 lanes at a time, on 32 compute tiles. Each tile's
first core configures the unpacker and the packer and hands the tile its instructions
inline; the unpacker copies the pairs' columns into the destination registers as
32-bit integers, the vector engine forms the rows, the packer copies them back. Each
32×32 product comes from four 23-bit partial products, corrected for sign; `fx` is
held as 30 low bits and the rest, and each row's terms are added or subtracted with a
carry after each. A pass is sized to its share of the pairs, in steps of 64.
- **`quantum_fabric::tensix`**: the compute tile's instructions the programs use, their
opcodes and fields as the device simulator's own table gives them; CI checks the
transcription against that table. **`quantum_fabric::vector_engine`** builds the
programs, the probe included; the assembler check covers them too.
- **Measured** on the device simulator, built from source: 66020 probe pairs agree, all
four pinned certificates, circuits up to 20 qubits match native, 3 of 3 kernel mutants
(the same three the other members carry) killed. Every certificate is labelled
simulator.
- **The gate as a portable tensor program** (`quantum_fabric::stablehlo`): the gate's rows
and the arithmetic probe as a StableHLO module, the input of compilers for tensor
processors, GPUs and CPUs. Every value is a 64-bit integer that never overflows
(`|e·a| < 2^62`; a row of four `fx` stays below `2^35`), so a compiler must give the
exact rows whatever it does with them. It is not a member: the program leaves the
crate, a compiler's runtime runs it, and its outputs are compared with the exact ones. The
`stablehlo_gate` example writes the module, its three mutants (the same three), a
batch of 4096 pairs whose gate and states reach the 32-bit edges, and the outputs a
build must give.
- **Measured** with two independent open-source tensor compilers, IREE 3.12.0 and XLA
(through JAX 0.11.2, the compiler tensor processors take this program through): exact
on the x86-64 CPU with each; on every Vulkan device of the hardware host (a discrete
GPU, an integrated GPU, and a software one); on the discrete GPU through CUDA with
each; through Metal on two generations of the macOS platform's GPU; with XLA on the
arm64 CPU. On the CPU each mutant fails where it should under each compiler; on every
other device, the third mutant's rows do. Builds for three GPU
generations the fleet lacks compile but are not run. `ci/tensor-program/check.sh`
holds each run to its outcome and fails a run that ends without stating one, so a
device that cannot run reads as a failure, never as a mutant caught. CI:
`wai-quantum-tensor-program`. That host's GPU runs it through HIP, built for the GPU's own
architecture, and on both of its Vulkan devices.
- **The products on a matrix unit, as a portable program** (`stablehlo::limb_module`): the
tensor-engine member's limb layout as one StableHLO `dot_general` of bf16 operands
accumulated in f32, the program a compiler maps to a matrix unit. With it, the example writes
a nine-bit-limb data set whose exact coefficients bf16 cannot reproduce, so a run that
matches them is not comparing. Exact under both compilers on the x86-64 and arm64 CPUs and
on the discrete GPU through CUDA. Not exact through one compiler's HIP build for the
integrated GPU, whose bf16 multiply returns values that are not integers; CI shows that
step and does not require it.
- **Breaking:**
- an OpenCL key is `opencl-<kind>-<vendor id>`, the device's `CL_DEVICE_VENDOR_ID` in hex
(`opencl-cpu-8086`, `opencl-gpu-10de`, `opencl-gpu-1027f00`, `opencl-gpu-1002`), not a word
of the vendor's name; `opencl::DeviceIdentity` gains `vendor_id`, which the member id
binds, and `vendor_slug` is gone;
- `quantum_fabric_vector::Isa` gains `Avx512` (`Isa::ALL` has four), and `native_isas` lists
every set written for the architecture;
- `quantum_fabric_dataflow::Chip` gains `vector`.
### wai-quantum-qpu 0.2.0
Builds against wai-quantum 0.6.0; nothing of its own changes.
### Parameter sets kept loaded between decodes
- **The store.** A decode through the integer operator set lowers its parameter set and
loads each graph, the bounds pass included; for the pretrained conditional-codec set
that costs more than the decode. `int_set_cache::LoadedSets` now keeps a set once it
has loaded, and the next decode through the same bytes takes it from there
([integer-operators](extensions/integer-operators.md) §8.6, informative note).
`int_payload::decode_int` and `int_payload::declared_cost` use it for `WIH1`, a
`WIV1`'s keyframe, `WIA1`, `WIS1` and `wai.neural.int_synth`, and so the neural sink
and `wai-web`'s `decodeWiv1`, `decodeWih1`, `decodeWia1`, `decodeWis1` and
`decodeIntSynth` do too; `decodeIntSynth` now decodes through `decode_int`, and a
refusal's message starts with its code. `int_synth::decode_from_spec`, which the
byte-equality crate shares, is now three steps (`check_payload`, `load_spec`,
`decode_loaded`) and loads its set every time. The entry points that read the tools'
JSON forms do not use the store.
- **The key** is the capability, the lowering, the profile the graphs were loaded under,
and the SHA-256 of the listing of the members the decode reads, each by its own
SHA-256 (`pin::listing_from`): the digest a pin over exactly those members names.
Never a name: two sets whose members share their names and differ in one byte have
two entries. Parameters `int_payload::resolve` built carry the listing digest it
already formed for check 5, so such a decode does not hash its set a second time;
the digest is used only for a capability that reads exactly the members it was formed
over, and any other key hashes the bytes.
- **What does not change.** An entry is made only from what a load returned, after it
returned, so a refused set is never kept: it is lowered, loaded and refused again on
its next decode, with the same refusal. Every check before the load runs as before,
in integer-payloads §7's order. Every output, refusal and declared cost is unchanged,
and no golden moved.
- **Memory.** A kept set is the set's, outside every decode's declared working bytes
([integer-payloads](extensions/integer-payloads.md) §7, informative note). The store
keeps its entries within a byte budget, 256 MiB to start with, evicts the least
recently used first, and keeps no set larger than the whole budget; a budget of 0
turns it off, and the set is then not hashed. `LoadedSets::set_budget` sets it, and
`wai-web` exports `setLoadedSetBudget` and `loadedSetStats` for a page. An entry is
counted as the shared allocation that holds the set, its graphs' heap bytes
(`wai_ops::Graph::heap_bytes`, new) and a fixed share of the store's indexes, never
less than it holds (`tests/int_payload_cost.rs`).
- **Tests.** `tests/int_set_cache.rs`: a second decode through each committed set is a
hit, by the store's count, with the bytes of a decode with the store off; sets one
byte apart never share an entry; a set the load refuses (an `int_synth` set the bounds
pass refuses; a conditional-codec set whose lowering admits no input range) stays
refused while a set one byte away is held; every refusal fixture keeps its code and
detail with its set held; the least recently used set goes first; threads decoding
one set keep one entry. The browser crate's check (`demo/verify_int_payload_wasm.mjs`) decodes `tiny`
twice and counts the hit.
### `wai.neural.int_hyper` and `wai.video.int_hyper` through the integer operator set
- **The decode.** `int_hyper_synth::decode_conditional_checked` (so the `WIH1` dispatch,
the keyframe of a `WIV1` with keyframe 1, the staged sink and the browser crate) plans
both networks as [integer-payloads](extensions/integer-payloads.md) §7 orders, checks
the payload's shapes against them, lowers `h_s` by `whm1/1` and `g_s` by `whm1/1` with
the RGB8 step ([integer-operators](extensions/integer-operators.md) §13.2, §13.0),
loads each under `acc64` with its bounds certificate, decodes the hyper-latent, runs
`h_s` through the operator set's interpreter, splits its output into `σ` and `μ`,
decodes the latent against the tables `σ` selects, and runs `g_s`. Every
convolution, transposed convolution and inverse GDN runs on the fast kernels behind
the interpreter. The graphs' `check` ranges are the checks the direct decode made
before each layer, so the same streams are refused (`field_range`). Every committed
output is unchanged, byte for byte (the conformance vector, the neural-keyframe clip,
and the pretrained set's image and clip regenerated in `wai-sota-vectors.yml`), and
every committed refusal fixture keeps its code. The direct decode is kept, test-only
(feature `legacy_int`), in the differential tests and their nightly run.
- **Planned breaks** (SPEC §8, integer-operators §15.3 item 3), as for
`wai.splat.int_codec`, in either network: new refusals of sets no committed corpus
contains, each `model_mismatch` after the payload's shapes are checked and before
either stream is decoded. A scan of every committed set of the two capabilities, and
of every committed `WIH1` payload wherever it is carried, finds none affected.
- **The inverse GDN on the fast kernel.** The interpreter now lends an inverse GDN to the
fast IGDN kernel, as it lends convolutions, wherever the values present keep every
term and norm within `i128` and the root and output within `i64`, which every
certified graph does; the splat and audio decodes use it too. Their outputs are
unchanged.
- **The unchecked decode is removed.** `int_hyper_synth::decode_conditional`, which ran
both networks with no exactness check, is gone; its callers (the examples, the
benchmark, the tests and the byte-exact neural-keyframe golden) decode through
`decode_conditional_checked`, so through the lowering with its `check` ranges, with
every output equal. The layer-by-layer guard `run_net_checked` stays while
`wai.neural.int_mlicpp` still runs its networks through it.
- **The tools' JSON models.** The browser crate's debug decode of the tools' JSON
form now refuses (`model_mismatch`) a JSON model holding a weight past `i16` or an
inverse-GDN `γ` past `i32`, the widths `WHM1` gives them and its lowering writes;
the committed models hold none.
- **Their `ops` member in the registry** ([integer-operators](extensions/integer-operators.md)
§11). `registry/capabilities.json` gives `wai.neural.int_hyper` and
`wai.video.int_hyper` the member `{"set": "wai-ops/1", "profile": "acc64", "subset":
"full", "lowering": "whm1/1", "stage": {"rans64": {"table": "t4097", "escape":
"lenient", "end": "canonical"}, "bucket": "ceil"}}`. SPEC §5's rows cite the payloads
([integer-payloads](extensions/integer-payloads.md) §4, and §3 for the clip) and the
lowering (integer-operators §13.2), and SPEC §7 names both capabilities among those
that decode through the operator set.
### wai-quantum 0.5.1: neural-processor, matrix-extension, vector-unit, signal-processor and dataflow members
Additive. The default features, `full`, and every pinned digest are unchanged.
- **The neural-processor member** (`quantum_fabric_npu`, opt-in, macOS only). A neural
processor computes in fp16 and does not document how it accumulates, so the member
never relies on it: each 32-bit operand is split into eight 4-bit limbs, and the
processor computes the fifteen coefficients `Σ_{i+j=s} a_i·b_j` of each product. Every
limb product is at most 225 in size and every coefficient at most 1800, so every value
it forms is an integer fp16 holds exactly, in any order. The host assembles each
product exactly and applies `lanes::apply_words`'s arithmetic.
- **Credited by placement.** The member writes its model (one multiply, one sum) as the
platform runtime's model description, compiles it at run time, and reads the
runtime's compute plan: every operation must be placed on the neural processor, or
the member is refused with the placement reported, so no CPU result is credited to the
processor. It is then probed and certified against the pinned corpus digests, and
the two model mutants (the multiply made an add, the sum a maximum) are caught.
- **The same program, portable.** `npu::onnx_limb_model` writes it in the portable model
format (ONNX, opset 17: one `Mul`, one `ReduceSum`), with fp16 or int32 tensors, and
`npu::expand_i32` lays out the int32 inputs; `examples/npu_onnx.rs` writes both models,
a batch of inputs and the exact products they must give, so a neural processor
reached through a runtime that reads that format can be checked against them.
- **Fixed** before release: `npu::f16_of(±2048)` subtracted past zero in a debug build.
- **The matrix-extension member** (`quantum_fabric_sme`, opt-in, aarch64 only): the
gate's fixed-point arithmetic in the processor's streaming vector mode (SME), 64-bit
lanes, by inline assembly; the host gathers each gate's pairs and checks each row's
range. Its instructions exist only in that mode, so a kernel that ran, ran there.
Detection reads the processor's feature flags (on Linux, `AT_HWCAP2`, which a
user-mode emulator supplies for its guest); a runner under an emulator declares it,
and every certificate carries that provenance. Three kernel mutants are compiled in.
- **The DSP vector-unit member** (`quantum_fabric_dsp`, opt-in, in a build for the signal
processor with its 128-byte vector extension). The gate's arithmetic in 32-bit lanes,
each 64-bit value a pair of words: the product is the vector unit's exact 32×32
multiply (the 32-bit operand times the other's low half, unsigned, plus its high
half, signed, sixteen bits up); the rounding constant's carry and each row's carries
and borrows go into the high word; the shift is arithmetic over all 64 bits. The
fabric gains the class `dsp`. Three kernel mutants are compiled in.
- **The CPU vector-unit members** (`quantum_fabric_vector`, opt-in): the gate's arithmetic
in the processor's own vector extension — `avx2` on x86-64 (the 64-bit arithmetic
shift, which the unit lacks, built from a logical shift and the sign bit), `sve` on
aarch64 and `rvv` on riscv64, both written once for every vector length (the length
set per pass and read back from the unit into the member's id). Silicon for `avx2`;
`sve` at 128, 512 and 2048 bits and `rvv` at 128 and 1024 bits under the user-mode
emulator. Three kernel mutants per instruction set.
- **The dataflow member** (`quantum_fabric_dataflow`, opt-in, Linux): the gate's arithmetic on
the first RISC-V core of each of 32 compute tiles of a dataflow accelerator, each over a
contiguous slice of a gate's pairs, two device generations (PCI 1e52:b140 and 1e52:401e),
driven through the device's own library at the level of configuration space, memory
windows, reset registers and clocks. The program comes from a new RV32IM
encoder (`quantum_fabric::rv32`), whose listings an independent assembler turns into
the same words; every 64-bit value is a pair of words. The library is the device's
open-source simulator, built from source, and every certificate is labelled simulator.
- **The signal processor as a processor.** The bit-identity digests, the frame fixtures and
the byte-pinning library tests pass for the processor's Linux target under its
user-mode emulator. Emulator releases before 11.0 test a whole predicate register
where the processor tests its low bit, in a branch on a predicate written in the same
packet; compiled code relies on the low bit, and one library test fails under such an
emulator. `ci/dsp/predicate_branch.c` shows which emulator does which, and the
suites run on 11.1.1, built from its release.
- **CI.** `wai-quantum-npu.yml`: the device tests on the macOS runner's neural processor,
and the same build on Linux, where the member is absent and says so.
`wai-quantum-sme.yml`: the matrix-extension member's device tests for aarch64 Linux
under the host's user-mode emulator, certificates labelled emulator; and the macOS
runner, whose processor has no SME, finding no member. `wai-quantum-dsp.yml`: the
signal processor's suites and the vector-unit member's device tests under the pinned
emulator, after the emulator's own branch probe, and the same build on x86_64, where
the member is absent and says so. `wai-quantum-vector.yml`: `avx2` on the container
runner's processor, `sve` and `rvv` under the host's emulator at the lengths above (each
leg checks the length the member read), and the macOS runner finding no member.
### `wai.audio.int_codec` through the integer operator set
- **The decode.** `int_audio::decode_typed` (so the `WIA1` dispatch, the staged sink and
the browser crate) plans the synthesis as
[integer-payloads](extensions/integer-payloads.md) §7 orders, lowers `model.wam` by
`wam1/1` ([integer-operators](extensions/integer-operators.md) §13.3, with its PCM
step), loads the graph under `acc64` with its bounds certificate, decodes the stream,
and runs the graph through the operator set's interpreter with `t_out` as its scalar
and the fast transposed-convolution kernel behind it. Its `check` ranges are the
checks the direct decode made before each layer, and the PCM step's range is the one
in which a sample's scaling fits `i64`, so the same clips are refused
(`field_range`). Every committed output is unchanged, byte for byte, and every
committed refusal fixture keeps its code. The direct decode is kept, test-only
(feature `legacy_int`), in the differential tests and their nightly run.
- **Planned breaks** (SPEC §8, integer-operators §15.3 item 3), as for
`wai.splat.int_codec`: new refusals of sets no committed corpus contains, each
`model_mismatch` before any stream is decoded, the PCM step's range among the `check`
ranges whose producer must meet them. A scan of every committed audio set and payload
finds none affected.
- **Its `ops` member in the registry** ([integer-operators](extensions/integer-operators.md)
§11). `registry/capabilities.json` gives `wai.audio.int_codec` the member
`{"set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "wam1/1",
"stage": {"rans64": {"table": "t4097", "escape": "lenient", "end": "canonical"}}}`.
SPEC §5's row cites the payload ([integer-payloads](extensions/integer-payloads.md) §5)
and the lowering (integer-operators §13.3), and SPEC §7 names the capability among
those that decode through the operator set.
### `wai.splat.int_codec` through the integer operator set
- **The decode.** `int_splat::decode_typed` (so the `WIS1` dispatch, the staged sink and
the browser crate) plans the networks as
[integer-payloads](extensions/integer-payloads.md) §7 orders, lowers `g_s.whm` by
`whm1/1` ([integer-operators](extensions/integer-operators.md) §13.2, with the RGB8
step of §13.0), loads the graph under `acc64` with its bounds certificate, decodes the
stream, and runs the graph through the operator set's interpreter with the fast
transposed-convolution kernel behind it. Its `check` ranges are the checks the direct
decode made before each layer, so the same latents are refused (`field_range`).
Every committed output is unchanged, byte for byte, and every committed refusal
fixture keeps its code. The direct decode is kept, test-only (feature
`legacy_int`): `tests/int_ops_differential.rs` decodes random sets and clouds both
ways, every push and for an hour per build profile each night.
- **Planned breaks** (SPEC §8, integer-operators §15.3 item 3): new refusals of sets no
committed corpus contains, each `model_mismatch` before any stream is decoded. A layer
with a bias of magnitude `2^63 − 1` or more, or a `check` range its producer cannot
meet, refused every decode with `field_range` when the network ran; a network with no
layer of tag 0, 1 or 2, or with a header `shift` or `gf` of 128 or more that no layer
uses, decoded where the shapes allowed. A scan of every committed splat set and
payload finds none affected.
- **The interpreter's memory.** A run releases each tensor once its last reader has
run, takes its inputs by value, and holds a transposed convolution's uncropped plane
in `i64` (each cell is within the certified `convT.acc`). With its set loaded, a splat
decode through the operator set holds no more than the payload's declared working
bytes (integer-payloads §7, *Memory and work*); the declared cost of a `WIS1` payload
is unchanged. `int_payload::declared_cost` now refuses, with the decode's code, a set
whose lowering does not load.
- **The byte-equality matrix** (`wai-byte-equality.yml`, `byte-exact-conformance`) now
decodes the committed `WIA1` and `WIS1` payloads of `wai.audio.int_codec` and
`wai.splat.int_codec` on every leg, under the reference and the fast kernels, to their
pinned PCM and attribute digests; it compiled neither decoder before.
- **Its `ops` member in the registry** ([integer-operators](extensions/integer-operators.md)
§11). `registry/capabilities.json` gives `wai.splat.int_codec` the member
`{"set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "whm1/1",
"stage": {"rans64": {"table": "t4097", "escape": "lenient", "end": "canonical"}}}`.
SPEC §5's row cites the payload ([integer-payloads](extensions/integer-payloads.md) §6)
and the lowering (integer-operators §13.2), and SPEC §7 names the capability among
those that decode through the operator set.
### wai-quantum 0.5.0: provenance, sampling members, device programs, and three more members
Breaking: `Certificate` gains a public field, `provenance`, and `FabricClass` is now
`#[non_exhaustive]`, so a class can be added in a minor release. A silicon certificate
hashes exactly as before; every pinned digest is unchanged. The `quantum_fabric` feature now
includes `quantum_toolchain`.
- **Provenance in the certificate.** `Provenance::{Silicon, Emulator(name), Simulator(name)}`,
inside the certificate's hash unless it is silicon. Placement ranks silicon first, then
emulators, then simulators, so a simulated member never stands in for hardware that is
present.
- **Sampling members.** The kernels `wai.quantum.circuit/sample` and
`wai.quantum.circuit2/sample`, the `Sampler` trait, `certify_sampler` (classical fidelity
against the exact distribution, over stated shots) and the `qpu-v1` corpus. `VirtualQpu`
samples a stated noise model (global depolarizing and readout flips) and says it is a
simulator. `VirtualDevice` adds a coupling map and a native basis: each circuit is
compiled, the program verified, and the gate errors charged for the program it would
run, routing included.
- **Programs for devices** (`quantum_toolchain`). `device::device_qasm` writes OpenQASM 2
or 3 in the dialect's standard gates only, with every multi-controlled gate lowered
exactly (dyadic angles throughout; refused, never rounded, past the table) and every
qubit measured into `c[i]`; `counts_from_bitstrings` and `counts_from_integers` read a
service's counts back. `native::compile` targets a processor's own basis (`rz`/`sx`/`x`/`cz`,
`prx`/`cz`, or `gpi`/`gpi2`/`zz` in turns) on its own qubits, routed over its coupling
map; `NativeProgram::verify` checks a program against its circuit by simulation before
it is sent. `from_qasm` now reads `ch`.
- **The OpenCL member** (`quantum_fabric_opencl`, opt-in): the exact 64-bit kernel in
OpenCL C, built by each device's own compiler; the ICD loader is opened when first used.
Certified bit-exact on GPUs and CPUs of several vendors; an OpenCL accelerator is refused,
since no fabric class names it.
- **The FPGA member, simulated** (`quantum_fabric_fpga`, opt-in): the datapath in Verilog,
each 32×32 product from four 16-bit partial products, compiled at run time by Verilator
into a cycle-accurate simulation. Its certificates say they were earned in a simulator.
- **The dense f64 state vector on accelerators** (`quantum_fabric::sv64`, with
`quantum_sv`): the CPU's own launches (diagonal with the exact-one skip, the one-qubit
formula, and ordered row sums), each an IEEE binary64 operation with no fused
multiply-add, on the CUDA member (`CudaMember::sv`) and on OpenCL devices with binary64
(`OpenClMember::sv`). Certified against the pinned corpus digest `SV_DEFAULT_PIN`; a
device without binary64 refuses, saying so.
- **wai-quantum-qpu 0.1.0** (new crate): clients for three quantum processing protocols
(`sampler_v2`, `prx_circuits`, `native_turns`), each compiling exactly to the device,
verifying before submission and reading counts back onto the circuit's qubits;
`QpuSampler` makes a service a fabric member; the `wai-qpu` command reads a device,
runs the bit-order probe, and certifies. `curl` is the transport, with secrets kept off
command lines. Tested against services that follow each protocol's published contract.
- **CI.** Workflows for the OpenCL member, the simulated FPGA member and the QPU clients;
the CUDA and OpenCL hardware jobs certify the f64 state vector; the hardware host
declares its devices in its runner's environment.
### wai-quantum 0.4.4: the fabric's CUDA member
Additive. The default features, `full`, and every pinned digest are unchanged.
- **The CUDA member** (`quantum_fabric_cuda`, opt-in, native targets only): the gate
kernel in CUDA C with exact 64-bit integers, compiled at run time by the runtime
compiler for each device's own architecture (`--fmad=false`), one launch per gate. The
driver and the compiler are loaded when first used, so a build with the feature runs on
a host without CUDA and finds no member there, saying why. Each device is probed against
the CPU's arithmetic and certified bit-exact by reproducing the pinned corpus digests; a
result that would leave 32 bits is refused, never returned.
- **CI.** `wai-quantum-cuda.yml`: the device tests on a hardware host's GPU (every
certificate, every kernel mutant caught), and the same build on a host with no CUDA.
### Reader limits for the extension formats' durations
A score, haptic signal, audio scene, splat4d clip or film declares its length in its
contract, and rendering it allocates in proportion. Nothing bounded the declaration
(found while fuzzing: a score's `mixdown` allocated `duration` samples outright).
- **The limits** (each extension's §2.1, `wai::limits`), shared with SPEC §2:
- score and haptics: `duration × channels` at most 2²⁷ samples;
- audio scene: `duration` at most 2²⁶, so its stereo floor is at most 2²⁷;
- splat4d (`n_frames`), film (`frames`) and a reel's shots together: at most 2²⁰
frames.
A reader may refuse past a limit (`duration_too_long`), when it reads the contract.
- **Film tick arithmetic.** `start_tick + f × ticks_per_frame` wrapped in release
builds and panicked in debug ones. A reader now refuses a schedule whose last tick
passes 2⁶⁴ − 1, and `frame_tick` saturates rather than wraps.
- **Tests.** `tests/extension_duration_limits.rs` rewrites a committed vector of each
format to sit exactly at its limit (it reads) and one past it (refused by name), and
checks the film schedule at and past 2⁶⁴ − 1. The old readers fail all three tests,
and every committed corpus verifies unchanged.
### Coverage-guided fuzzing of the reader
`wai-rs/fuzz/` adds four libFuzzer targets, built by cargo-fuzz with AddressSanitizer:
- `envelope`: the `WAI1` and `WAI2` readers and the listing form;
- `int_payload`: every integer capability, against the committed parameter sets;
- `media`: the decoders of the floor and the recommended set;
- `containers`: the extension containers.
They are seeded from the repository's own vectors. `wai-fuzz.yml` runs each target for
15 minutes every night and keeps the corpus between runs; it never runs on a pull
request. `fuzz/run.sh` runs the same targets anywhere a nightly toolchain is installed.
SECURITY.md now says what is fuzzed and what is not.
- **Found and fixed.** In its first seconds, `containers` found the audio scene reader
reserving entries for a `u32` count read from the input, before it read one: 48 GiB
asked for by a few bytes. The world, reel, score, haptic and splat4d readers did the
same. Each now reserves no more than its remaining bytes could hold.
`tests/container_reservations.rs` rewrites a committed vector of each to declare
2³² − 1 entries in four bytes and requires a refusal with no allocation past a few
megabytes; on the old readers the world asset table alone asked for 256 GiB.
### Reader limits for picture, audio and video payloads
The last decoders that allocated what a payload declared now hold it to limits
(SPEC §2), as manifests and text payloads already were.
- **The limits.** A reader must accept a picture up to 65 536 pixels on a side and
2²⁷ pixels, audio up to 2²⁷ samples counting every channel, and AV1 up to 2²⁰
temporal units of such pictures. It may refuse past them (`image_too_large`,
`audio_decoded_too_long`, `video_too_large`), from what the payload declares wherever
it declares it. The writers refuse the same.
- **Payload framing** (SPEC §5). The AV1 framing that the reference has always used,
and packaging §7 writes, is now in the standard itself. So are the FLAC and Ogg Opus
forms, and the rule that an image payload decodes only as its own format.
- **Defects fixed in the reference decoders.**
- A FLAC `STREAMINFO` declaring 2³⁶ − 1 samples made the decoder ask for 256 GiB up
front.
- A 24-bit FLAC decoded to about ±256 instead of ±1.
- An Ogg Opus stream whose source rate was not an Opus rate (44.1 kHz) was refused.
- `png_decode` and `jpeg_decode` decoded any format the bytes resembled.
- `jxl_decode` refused a deeper-than-8-bit picture and returned one or four channels
where it promised three.
- The AV1 decoder decoded every packet before truncating to `n`. It panicked on
monochrome, misread 4:2:2, 4:4:4 and 10- and 12-bit frames, and let dav1d allocate
whatever a sequence header declared.
- **Streaming AV1.** `av1_decode_each` hands on one frame at a time. `av1_decode` and
`wai_video_av1_decode`, which return every frame at once, refuse past 4 GiB of output
from the header (`video_decoded_too_large`). `wai decode` streams frames to its file.
- **Unchanged.** Every 8-bit 4:2:0 frame decodes byte for byte as before. The AVIF path
now calls libavif directly with the reader limits as its decoder limits (it drops
`libavif-image` and the rav1e build inside libavif), and `include/wai.h` is
regenerated.
- **Tests.** `tests/media_decode_bounds.rs` checks each limit at its edge and one past
it, and each defect above. CI runs it in `codec-wrappers`. Five mutations, each
reintroducing one defect, fail it.
### Vector graphics (Draft): a title card and credit roll in the corpus
`wai/vector-conformance/` gains `motion/stroke-titles`: a 1280 × 720 document at 25 fps
in the default stroke set, a lower third that slides in, holds and fades as a group, and
a credit roll that rises three design pixels a frame. Each line's window is cut to the
frames at which it meets the canvas (graphics-vector §5.4, *Where a block is*). Every
frame's digest is in its expected file, so the native, WASI, Node and three-engine
browser legs and the independent checker each check all of them.
### `wai.neural.int_synth` through the integer operator set
The first capability whose reference decoder decodes through `wai-ops/1`.
- **The decode.** `int_synth::decode_bytes` checks the payload
([integer-payloads](extensions/integer-payloads.md) §13), lowers the set by
`int_synth/1` ([integer-operators](extensions/integer-operators.md) §13.1), loads the
graph under `acc64` (load steps 1–8, the last being the bounds certificate) and runs it
through the operator set's interpreter, with the fast transposed-convolution kernel
behind it (`wai-rs/src/int_ops.rs`). Every committed output is unchanged, byte for
byte, on every platform the byte-equality matrix covers and in the browser crate, and
every committed refusal keeps its code (`payload_malformed`). The direct decode it
replaced is kept, test-only (feature `legacy_int`): `tests/int_ops_differential.rs`
decodes random sets and payloads both ways and requires the same output or both
refusals outside the planned breaks, and `wai-ops-differential.yml` runs it for an
hour in each build profile every night.
- **Planned breaks** (SPEC §8, integer-operators §15.3): new refusals of input no
committed corpus contains. `wai.neural.int_synth` refuses a payload whose reserved byte
is not 0, whose `C` is 0 or whose latents exceed `2^26` values; a set with no layers,
with a layer whose `out_ch`, `k` or `stride` is 0, with a field of `2^32` or more, or
with a shift of 128 or more; a set or payload a cap refuses (a tensor, or a transposed
convolution's uncropped output, past `2^26` elements); and a set whose certificate does
not meet `acc64`. A shift from 64 to 127, which a build used to mask or panic on, now
has its exact meaning. A scan of every committed `int_synth` set and payload finds
none affected. The text now states the two rules the lowering already made and the
payload text did not: at least one layer, and `out_ch ≥ 1`.
- **Its `ops` member in the registry** ([integer-operators](extensions/integer-operators.md)
§11). `registry/capabilities.json` gives `wai.neural.int_synth` the member
`{"set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering":
"int_synth/1"}`, and the schema describes the member. SPEC §5's row cites the payload
and set ([integer-payloads](extensions/integer-payloads.md) §13) and the lowering
(integer-operators §13.1), and SPEC §7 says that a `NeuralIntegerExact` capability
that decodes through the operator set names it in its `ops` member.
### Vector graphics (Draft): the sink as a WebAssembly 1.0 module, in three browser engines
Additive. No payload, outline set, vector or digest changes.
- **The sink's exports.** `wai-vector/wasm` exports the sink of
[graphics-vector](extensions/graphics-vector.md) behind a C ABI, with no bindings
generator and no imports. `wv_add_set` holds an outline set a payload may name;
`wv_open` loads a payload to a handle, or to its refusal's code; `wv_render` gives a
frame's `RGBA8P` at a listed size and rate, or the request error's code; `wv_cost`
gives a listed size's cost; `wv_transcript` gives the transcript, and `wv_projection`
the projection at a frame of a listed rate, each as the JSON of §5.4, or the request
error's code; `wv_sha256` gives a digest; `wv_close` releases a handle. A result is
read at `wv_out_ptr`, `wv_out_len` bytes of it. No export takes or returns a 64-bit
integer: a frame number goes in as two halves.
- **The package.** `wai-vector/wasm/build-pkg.sh` builds the module a site serves: the
sink's exports alone, with no glue script. It is WebAssembly 1.0 and nothing later:
no SIMD, no threads, no bulk memory, no sign-extension operators, no reference types.
The target is `wasm32v1-none`, whose precompiled `core` and `alloc` are 1.0
themselves, so the crate is now `no_std` and carries its own allocator. The build
then reads the module back: `wasm/check-mvp.mjs` decodes every section, type, segment
and instruction, and refuses anything later than 1.0, any import, any 64-bit integer
at an exported function, any floating point, and any exported function the script
does not list.
- **Three browser engines.** `wai-vector/browser/run.mjs` serves a page, the package and
the corpus, and in each engine loads every payload of `vector-conformance` to its
expected code, checks its SHA-256 and each listed size's cost, and renders its frames
through the page's hook, `window.__wai_overlay.frame(size, rate, g)`, which returns
the renderer's own buffer: every listed frame at sizes up to 1280 × 720, and sampled
frames above. The page hashes each frame with the module's SHA-256 and with the
platform's digest. The sampled frames are also sent to Node as bytes, and Node hashes
them itself. Where a payload's expected file states a text record, the page's hook
also gives the sink's transcript and its projection at every frame of every listed
rate, `transcript()` and `projection(rate, g)`; written out as the record's lines,
they must be the committed `<name>.a11y.jsonl` byte for byte. The recomputation
module then recomputes every expected file and authoring form in each engine.
Nothing is read from a canvas. A run passes only when all three engines ran: a
missing engine is a failure.
- **CI.** `wai-vector.yml` gains the `browser` job: the package built and read back,
then the three engines, in a container image pinned by digest.
### Vector graphics (Draft): text and accessibility, §5 of `wai.graphics.vector`
- **[graphics-vector](extensions/graphics-vector.md) §5 is written.** A payload carries
its words beside its glyphs, and this revision says what a sink computes from them and
what it owes the viewer:
- **The projection and the transcript, byte for byte (§5.4).** A block's active set is
the union of the effective windows of the runs and paths that draw it, cut to the
document's window, sorted, with windows that overlap or touch merged. The projection
at a frame is every block active at the frame's tick, in reading order; the
transcript is every block with its active set. Both are JSON with a fixed member
order and exactly three string escapes. A block's `purpose` is written as the byte
carried, an unregistered code included; `mode` is in every node; a ruby link is
written as the base block's index with the range's start and length; `decorative`
blocks are in neither. Opacity, position and clips do not enter either value. A
payload's *text record* is its transcript and every frame's projection at every
listed rate, one value a line: text-equivalence is byte equality of it.
- **Encoder rules (§5.1, §5.3)** that a verifier checks and a sink does not: one base
direction for a block, one joining sequence in one run, vertical text and its
`mode`, Normalization Form C, every byte of a string in exactly one cluster, no
zero-glyph cluster for a visible character, and no colour glyphs.
- **The purpose vocabulary (§5.2),** with how each purpose is exposed and whether it is
announced by default.
- **Exposure (§5.5).** A presenting sink exposes the current projection to the
platform's accessibility interface, updates it when the projection changes, inserts
strings as text and never as markup, and keeps within stated announcement limits.
What assistive technology then says is not claimed.
- **Whether the words match the glyphs (§5.6):** the chain from a parent font to an
outline set, from a text block to glyph IDs, and from glyphs to the overlay layer;
what an authoring receipt records; and what the chain does not reach.
- **Captions under the viewer's control (§5.7).** Captions travel as timed text. A
sink that cannot read the platform's caption preferences does not draw `caption` or
`subtitle` items, and a frame it draws without them is not the published frame.
- **`wai-vector`: the text layer.** `wai_vector::text` computes `text_hash`, block
identities, effective windows, active sets, the projection, the transcript and the
text record, in a sink's build, and a verifier's check of the cluster rule. The tool
gains `wai-vector a11y` (the text record, the transcript, or one frame's projection)
and `wai-vector text` (the text digests and each block as a reviewer reads it). The
corpus-seeded fuzz holds the text layer of every payload it accepts: the transcript
is JSON that gives back each tag and string, and a projection lists exactly the
blocks the frame procedure draws at that tick.
- **`vector-conformance`: the `a11y` group.** Each payload's expected file states the
SHA-256 of its text record, and the record is committed beside it, so every fabric leg
recomputes it: the worked example of §5.4, windows and a fade to nothing, a
`decorative` block, text alternatives of paths, reading groups, ruby over part of a
base, registered and unregistered purposes, merged and clipped intervals, windows cut
by enclosing groups, the three writing modes, four rates over one document, strings
that take each escape and strings that take none, and a payload with no text. The
`canonical` and `refusals` groups gain text-block cases: an ungrouped block between
groups, ruby links to a later block and ranges that end inside a character or at the
string's end, and pairs that pin where `TEXTS` sits in the record pass and that the
text-block rules come before any outline set is read.
### Reader limits that bound memory: manifest values, and text decode
Two reader limits join the manifest length limit (SPEC §2, *Reader limits*), and with
them a reader's memory is bounded by what the specification states, not by what an
envelope asks for. `SECURITY.md` listed both as known gaps; it now states the bounds
and the tests that measure them.
- **Manifest values.** A reader need accept no manifest of more than 2²² JSON values,
the manifest object and every value in it at any depth. The reference readers count
the values before they build anything, refuse one past the limit
(`manifest_too_many_values`), and build what they read at the size counted: at most
128 bytes a value and one a byte on a 64-bit target, where a manifest of small
values at the length limit took several gigabytes to parse. The stdlib Python
verifiers refuse the same manifests, and the HTTP corpus holds a case at the limit
and one past it.
- **Text decode.** A `wai.text.zstd` or `wai.text.xz` payload decodes to at most 2²⁷
bytes and declares at most 2²⁷ bytes of window or dictionary; a reader may refuse
more (`text_decoded_too_long`, `text_window_too_large`). The reference decoders
write into a buffer never sized past the limit, refuse a Zstandard frame that
declares a longer content size before decoding anything, and give the decoding
library the window limit, so a declaration past it is refused from the stream's
header. The reference encoders refuse input past the length limit. The C entry
points `wai_text_zstd_decode` and `wai_text_xz_decode` are bounded the same way; the
header gains `TEXT_DECODED_MAX_LEN` and `TEXT_WINDOW_MAX_LEN`.
- **Planned break for writers.** A conforming encoder writes no manifest past the value
limit and no text payload past either text limit. No committed envelope or vector is
past any of them, and no digest changes.
- **The codec wrappers in CI.** `wai-conformance.yml` gains `codec-wrappers`, on the
macOS runner: the wrappers' fuzz smoke, the text decode limits and the C entry
points, built with `codecs`. The step fails if any of its test binaries ran no test,
which is what a build without the feature does.
### Integer operators: the reference crate, the bounds pass and its certificates
The crate, the tool and the corpus that
[integer-operators](extensions/integer-operators.md) (Draft) names exist. No capability
decodes through them yet: each capability's reference decoder is still the one its
payload extension names.
- **`wai-ops`**, the reference crate, `no_std` with `alloc` and no dependencies: the
512-bit saturating integer of §8.1, the canonical JSON writer of §8.5, the `WHG1`
parser and writer, the load-time and run-time plans, every graph operator of §5, the
stage operators E1–E13, and the lowerings of §13.
- **Load step 8.** `wai_ops::bounds` runs the bounds pass of §8.1–§8.5 and writes the
canonical certificate. The interpreter runs only a loaded graph, so nothing decodes a
parameter set its certificate does not admit. `interp::run_observed` reports every
value a run forms; `bounds::Within` holds a run to its certificate, and every golden
decode runs that way.
- **`wai-verify`**: `bounds`, `lower` and `vectors`, with `--check`, `--witness` and
`--advise`.
- **`wai/ops-conformance/`**: per-operator, stage, graph, load and lowering vectors, the
lowered graph of every committed parameter set, and each set's certificate.
- **Two certifiers.** `tools/wai_ops_check.py`, written from the extension text and
sharing no code with the crate, recomputes every vector and every certificate byte
for byte.
- **`wai_ops::limb32`**, a reference model of the 32-bit arithmetic the `acc32` profile's
two products need, checked against 64-bit arithmetic.
- **Model checking.** The operator set's harnesses are in `wai-ops/src/proofs/`, hosted
by `wai-verify` and run by `wai-formal-verification.yml`. A harness that does not yet
verify within the job's budget is kept, behind `cfg(kani_open)`, and is not run or
counted as proved.
- **`wai-ops.yml`** runs the crate's tests and `vectors verify` natively, under
wasm32-wasip2 and under wasm32 in Node, and runs the checker.
### wai-quantum 0.4.3: metered runs, placement by run, and the fabric wrapper
Additive. The default features, `full`, both implementations of the WebGPU member, and
every existing pin and digest are unchanged; runs, receipts and members stay unmetered
unless a caller supplies a meter.
- **Run meters.** `quantum_fabric::RunMeter`: `start` just before a run's first
submission, `stop` after its last readback, returning the microjoules in the window and
their `EnergyClass`, or none when the window cannot be attributed to the run alone. The
trait is member-neutral, and no implementation ships in the crate. A reading reaches a
result only when `check_label` accepts it; otherwise the run is unmetered (zero, no
class). Every start is matched by one stop, a refused run's reading is discarded, and a
meter that cannot start is never stopped.
- **The WebGPU member, metered.** `WebGpuMember::simulate_metered(c, start, meter)`
returns the state with the accepted reading. Its window runs from the first submission
to the last readback: the device's work, and the host's waits and copies between them;
planning, buffer creation, the upload's staging and the conversion to a state vector
are outside it, and the member's `detail` says so. A circuit refused
before any submission opens no window. `member_metered(corpus, meter)` certifies as
`member` does and gives each entry the figure its runs earn;
`blocking::{simulate_metered, member_metered}`.
- **Placement figures from metered calibrations only.** `EnergyCalibration` (`new`,
`add`, `runs`, `finish`) turns metered runs into a `KernelEntry::energy` figure: the
total energy over the operations performed (`pair_updates`: one pair of amplitudes under
one gate), in nanojoules rounded up, with the runs' class, whose uncertainty is the
largest of the runs' plus what the rounding adds, over their windows together. An
unmetered run, a refused label, a second class, or an uncertainty that no longer fits
its field once the rounding is added leaves no figure.
`detect_local_metered(corpus, meter)` gives the CPU reference its figures. Because
`place` ranks a known figure before none, figures go on the CPU reference and on the
members placement compares it with, or on none. `place` is unchanged; `place_for` and
`Fabric` hold the rule themselves, and read figures on only some candidates as figures
on none.
- **Placement for a run.** `place_for(members, kernel, required, predict_pj)` places by
the least predicted picojoules when every candidate has a prediction, ties falling as
in `place`. Otherwise predictions are not used, and the order is `place`'s, except that
figures on only some candidates count as figures on none. Under synthetic models with a
fixed cost per GPU run, a 3-qubit circuit stays on the CPU and a 24-qubit one goes to
the GPU.
- **The fabric wrapper.** `Fabric` holds members with their `Runner`s (`CpuReference`,
and `WebGpuMember` for its own implementation) and optional meters (`add`,
`add_metered`, `set_model`, `order`, `run`). `run` places the circuit, falls back down
the placement order when a member refuses with `MemberError::{TooWide, OutOfRange,
Memory, Timeout, Device}` (`falls_back`), stops at an invalid circuit or a defect, and
seals the `FabricReceipt` with the member and implementation that produced the result
and its accepted reading. The refusals (`Refused`) are listed beside the receipt in
`FabricRun`, or in `FabricError`; the receipt's signed bytes are unchanged.
`WebGpuError` converts into `MemberError`. A runner states the member it is
(`Runner::member_id`), and a run that reaches a member paired with a runner of another
id is refused there as a defect, so a receipt cannot name a member that did not produce
the result. `ready(future)` gives the output of a future that is ready on its first
poll, which is how a native caller drives `run` without an executor; `&R` is a `Runner`
and `&mut M` a `RunMeter`.
- **Tests.** CPU-side tests, native and under wasm32-wasip2 (each required by name in the
`quantum-wasip2` job), cover the readings, the calibration, figures on both members or
neither, `place_for`, the fallback, and metered fabric receipts. The device test
`g13_metered_runs_and_the_fabric` runs the member under a test meter, calibrates it on
random-v1 with the pinned digests, and runs the fabric with the member held to three
qubits: a wider circuit falls back to the CPU reference, a narrower one is produced on
the member and metered. It runs the tile implementation the same way, whose receipt
names it, and requires a member paired with another member's runner, or with the other
implementation's, to be refused. The member script requires it, and a fabric line per
declared adapter.
### wai-quantum 0.4.2: tile runs, and the WebGPU member in a browser
Additive. The default features, `full`, the per-gate implementation and every existing pin
and digest are unchanged.
- **Tile runs: a second implementation, `wgsl-lanes32-tile/54e443520c020013`.**
`plan::Config` (`PER_GATE`, `TILED`; `tile_size`, `run_cap`), `plan::Step::Tile`,
`compile_with`, `schedule`, `execute_from`, `PlannedGate::{new, is_identity,
is_well_formed}` and `TILE_BITS_MAX`; `PLAN_REVISION` is 2. A tiled plan applies each
maximal run of consecutive gates that act inside a tile (diagonal gates on any target,
others with their target inside the tile) to tiles of up to 1024 amplitudes, in
op-log order, and leaves out exact identities only (circuit/1's `P(19..=32)` is
`2^30 − 1`, not the identity, and stays). The CPU executor mirrors a tile kernel's
index arithmetic and equals the per-gate program on any words; every configuration
certifies with the lanes32 pins. A program whose gate's `diag` disagrees with its
words, or whose op index is `u32::MAX`, is refused by the executor and by the member.
`wgsl::TILE_SHADER` (`lanes32.wgsl` followed by `lanes32_tile.wgsl`, entry point
`apply_tile`: one workgroup of 256 per tile, the tile updated in place in the state
planes, a storage barrier between gates, the tile size in the run record, never in
the text; a run record whose tiles or grid do not cover the state exactly once is
refused) has its own id (`tile_implementation_id`), records (`tile_gate_record`,
`tile_run_record`), grid (`tile_grid`) and mutants (`TILE_MUTANTS`, with the
detectors `Race` and `Malformed`, `race_witness`, `malformed_tile_run`).
`WebGpuMember::tiled()` gives the same device running it, probed and certified on its
own; the member splits a run into dispatches of at most `SUBMIT_BUDGET` pair-updates.
Discovery opens per-gate members, which are the fallback. Given both implementations'
entries for one member and no energy figures, `place` breaks the tie by
implementation name and places the tile implementation (`wgsl-lanes32-tile/…` sorts
first). The checker holds `TILE_SHADER` to its own rules, barrier placement and
barrier uniformity included, and its SPIR-V to the validator; the member's device
tests, the member script and the browser leg require the four pinned digests under
both implementations and every tile mutant caught. `tests/fabric_expected.rs` (not
in the package) holds `quantum-conformance/fabric/expected.json` to the crate.
- **The member in a browser.** wai-web's feature `quantum_webgpu` binds the member for JS
(`quantum_webgpu_discover`, `QuantumWebGpu`: `certify`, `statevector_hash_hex`) in a
bundle of its own (`--out-name wai_quantum_webgpu`); the default build and its bundle
are unchanged. `demo/quantum_webgpu.html` runs the member in a worker, its kernel
compiled by the browser's own WGSL compiler. `quantum-conformance/browser_webgpu.mjs`
drives the page headless and requires the four certificates bit-exact with the pinned
digests (`quantum-conformance/fabric/expected.json`), every vector's statevector hash,
every reject file refused with its class, and a circuit wider than the member holds
refused. The `checks` job builds the bundle, and a `browser` job runs the page on macOS.
### wai-quantum 0.4.1: the fabric's WebGPU member
Additive. The default features, `full`, and every existing pin and digest are unchanged.
- **Certification one case at a time.** `quantum_fabric::Certification` (`new`, `add`,
`cases`, `finish`) folds a member's outputs into a certificate as they arrive, for a
member that produces them asynchronously. `certify` is now a loop over it and gives the
same certificates.
- **Corpora.** `Corpus::Random { seed, cases }` is the existing random corpus
(`random-v1`). `Corpus::Fabric { seed, cases, wide }` (`fabric-v1`) continues the same
stream after it with circuits of 11 to 17 qubits (`lanes::random_wide_circuit`), so that
every dispatch shape a 32-bit-lane member uses is exercised. `FABRIC_DEFAULT` is seed 7,
200 + 24 circuits; `GENERATOR_FABRIC`, `GENERATOR_FABRIC2` and `fabric_generator` name
it per gateset, and `Corpus::{generator, name, circuits}` describe any corpus. The
fabric-v1 lanes32 digests are pinned: `41c726a0…3df7` (circuit/1) and `9a84b55a…7809`
(circuit2). `lanes::certify_corpus` certifies the lane core on any corpus;
`certify_lanes` is it on the random corpus, with the same generator string.
- **The lane core, word by word.** `lanes::GateWords` and `gate_words` (a gate as the
eight 32-bit matrix words and the target and control masks a kernel reads),
`fxmul_wide` (the exact 64-bit `fx`), `PROBE_EDGES`, `apply_words` and `run_words` (the
general 2×2 path computed from exact 64-bit values, flagging a gate exactly when the
reference's result leaves 32 bits, and refusing malformed words), `simulate_checked`,
`output` and `reference_output`. `lanes::horizon(n, gs)` is the gate count below which a
valid circuit provably cannot leave 32 bits, computed in integers from the gateset's
table: 46,340 (circuit/1) and 46,339 (circuit2) at 26 qubits. The module documentation
and `fxmul` no longer claim that every result of a valid circuit fits in 32 bits; it
holds within the horizon.
- **`quantum_fabric::plan`**: `Program` (one step per op, the start reduced modulo 2^n),
`PlannedGate`, `Step`, `compile`, `LaneRun` and `execute`, the CPU executor a member's
output is held to; `PLAN_REVISION` is 1.
- **`quantum_fabric::wgsl`**: `SHADER`, the lane core transcribed into WGSL
(`src/quantum_fabric/lanes32.wgsl`: unsigned 32-bit values only, an exact 64-bit copy
of every result, literal shift amounts, no float, division, conversion, workgroup
memory or barrier); `implementation_id()` (`wgsl-lanes32/dc8e19f61e0e87ab`, a BLAKE3
over the shader text and `HOST_LAYOUT`, so any edit is a new implementation that earns
its certificates again); `gate_record`, `Grid`, `grid` and `gate_grid` (the 64-byte gate
record and the dispatch grid; `WORKGROUP` and `X_GROUPS_MAX` are 64); `AdapterIdentity`
with `member_id` (`local/gpu-…` or `local/swgpu-…`, sorting after every
`local/cpu-<arch>`), `key` and `class`; and `MUTANTS`, `mutate` and `Detector`, the
shader mutants every device must catch. The module documentation carries the argument
that the kernel is bit-exact. `wai/tools/quantum-wgsl-check` (unpublished) holds the
shader to that argument's rules on naga's IR and translates it to MSL, SPIR-V and HLSL.
- **The WebGPU member, feature `quantum_fabric_webgpu`** (opt-in, not in `full`). The
kernel through the platform's GPU interface natively (Metal, Vulkan, DX12) and through
a browser's WebGPU on `wasm32-unknown-unknown`; wgpu 30.0.1 enters only those two target
tables, and on every other target, `wasm32-wasip2` included, the module is compiled
out. `discover()` opens each adapter at the WebGPU downlevel limits with only the buffer
limits raised, pins the DX12 shader compiler (translator `naga+dxbc`), probes the
device's arithmetic against the lane core, and lists each adapter it cannot use with
the reason (`Discovery`, `Refusal`). `WebGpuMember` runs lanes, programs and circuits,
certifies a gateset on a corpus, and builds a fabric `Member`; a bit-exact member
reproduces the pinned random-v1 and fabric-v1 digests. A result that would leave 32
bits is refused (`WebGpuError::OutOfRange`), never returned. Every entry point is async
with no executor; natively `blocking` resolves each call in one poll, and a future still
pending is an error. Natively each submission is waited on before the next, so
`POLL_TIMEOUT` bounds one chunk of gates, not a run; a failed wait, which the GPU
interface reports by panicking, is caught and returned as `WebGpuError::Device`, and
the member treats its device as lost from then on (a `panic = "abort"` build still
aborts). With no energy figures, placement keeps the CPU reference, and a receipt over
a member's run is unmetered.
- **wai-rs.** The feature `quantum_fabric_webgpu` forwards to it and re-exports the
module. `wai_quantum_vectors verify <dir> --webgpu` runs the corpus check once per
WebGPU member, each state computed by the member; each accept file must also run on the
member as it does natively, so the 255-qubit files are refused as `too-many-qubits`.
Without the feature `--webgpu` is an error; with no member it exits 2.
- **CI.** `wai-quantum-webgpu.yml` checks the shader under naga and a SPIR-V validator,
the browser build, the feature's absence from the `wasm32-wasip2` dependency tree, the
shader file in the package, and the MSRV; and runs the member's tests
(`scripts/wai-quantum-webgpu-member.sh`) on software Vulkan, Metal, and a discrete GPU
through Vulkan. Each member job declares the adapters its runner must have, and fails
if the adapters found differ, if any test did not run, if a declared adapter lacks a
bit-exact certificate for each pinned digest, or if a shader mutant survives. The
`wasm32-wasip2` job requires the fabric's new unit tests by name.
### Packaging: refinement layers, stills, a piece's ladder, live windows and delivery
- **Refinement layers in packages** ([packaging](extensions/packaging.md) §5.4). For
each refinement rung of a piece's ladder the packer writes a `wai.video.int_refine`
object over the piece's `wai.video.int_motion` base: the manifest `wai stage refine`
writes, and a `WIR1` from `wai-rs`'s refinement encoder (`staged::wir1::encode`), one
unit per frame, toward the source from the stage below as a sink reconstructs it. Each
layer is applied to the decoded base by the reference sink before it is kept; the
index states each prefix's tier, digest, SSE and deadline.
- **A piece's ladder, and stills** (§5.7). A rule's `then.ladder` chooses the rungs a
piece is coded in; otherwise the component's first base rung, its refinement chain and
its floors. §3.4 item 10 now requires a rule's ladder to name exactly one base, every
refinement over a rung it names, and every floor of its component. A base of
`wai.image.png` is a still: the piece's first frame, held for the piece, as a PNG every
byte of which is stated (the `Sub` filter, one fixed-Huffman deflate block whose
matches are stated), so an independent implementation writes the same file.
- **Catalogs** (§8.3, §8.4). The MoQ catalog is now a stage set of moq-streaming-format
§4.1, written by `wai::staged::moq`: `wai_staged` on every track, a receipts track and
an alias per media track, `depends` on a track of every layer below; it replaces
`priority` (MOQT's own, §8.5) and `wai_layer`, which the registry no longer lists. Over
HTTP, whose variants cannot mix bases with no capability in common (http-delivery §4.2),
a still's piece is played from the floor; its stage view and MoQ tracks list it.
- **Live windows** (§9.2). `wai-pack live` packs a Y4M stream from standard input as it
arrives, with the coder the on-demand packer uses, in windows sealed by version-2
receipts chained `live-window`; its bases and refinements are the on-demand package's,
byte for byte. `wai-pack listing --primary` lists what the two share. It writes as it
goes: each object when it is published and each window when it is sealed, every file
whole, and the plan file and `LISTING` at the end. It does not keep an object's bytes
once written, or a record per file: each listing line goes to a hidden file and is
sorted into `LISTING` at the end. A source that fails mid-stream keeps every sealed window and the objects
it lists, removes the rest, and exits 1 saying what it kept.
`wai::pin::listing_from_sha256` writes a listing from per-file digests.
- **Stills read by PNG's rules** (§5.7). Both reference readers take a PNG of 8-bit RGB
without interlace and agree on what that admits. They read a suggested palette,
ancillary chunks and split image data. They refuse an interlace, bytes after `IEND`
or after the zlib stream, a short row, a zlib window past 32 KiB, and every other
chunk fault (`stills/`). Both inflate as the zlib library does: no over-subscribed
Huffman code, and no incomplete one but a lone one-bit code. wai-pack's reader has
its own inflater, and `png_diff.py` runs it against `pack_verify.py`'s zlib.
- **The longest piece** (§3.4, §4.4). `max_ms` is at most ten minutes, and at most
`2^24` units on every component's timebase (`policy-piece-bounds`). A verifier
refuses a piece longer than the policy's `max_ms` before it decodes a frame. It
checks each floor object's unit count before decoding (§11.3). It hashes a still's
frame held for its piece without repeating the frame.
- **Manifests checked by both verifiers** (§11.3). A base's manifest must be §5.2's and a
refinement's §5.4's (`object-manifest`, a new reason). A base that does not decode
is `prefix-digest`.
- **A repeated object over HTTP** ([http-delivery](extensions/http-delivery.md) §4.2).
A repeated object contributes once, with its layers, and a layer joins every
occurrence of its base. `http_verify.py` and `wai-rs` now agree on a playlist's
codecs and floor where a piece repeats. `pack_verify.py` checks every golden playlist
with `http_verify.py`'s union, floor and adjacency checks.
- **One stall bound.** `stage_measure::sim::stall_bound_for` is the only bound: a
model's, which takes each refinement's own timeout. wai-web's simulator prints it, as
the native tool does.
- **Delivery, simulated** (§8.9). `wai_pack::delivery` and `wai-pack sim` run
staged-measurement's simulator over what a live packer publishes and what a class
subscribes to: per-track delivery timeouts, §8.5's priorities, the subscriber's cap,
group receipts on each track's chain. The simulator's model gains a refinement's own
`timeout_us`, and §10.4's bound takes the longest refinement timeout
([staged-measurement](extensions/staged-measurement.md) Appendix B); its corpus adds a
per-track model.
- **The corpus**: the golden `video-staged-01` (a moving shot, a bumper played twice, a
still shot, a second moving shot; two refinement layers, a still, the event's deadline,
three classes), its supplied floor and how it was written (`supplied_floor.py`),
`staged-mutations/`, `delivery/`, `stills/` and `live/` (the golden packed live in
windows of two pieces, whose chain `pack_verify.py` checks). `stage_diff.py` runs
refinement layers, stills (wide ones among them, so every deflate distance code is
used), piece plans and deliveries differentially against `pack_verify.py`, which
re-derives every layer and still from the source byte for byte. `sim_diff.py` runs the
delivery simulator differentially against `sim_verify.py`. `pack-refinement-unsupported` and
`pack-rule-ladder-unsupported` are gone.
- The base golden `video-base-01` keeps its objects, index and receipt; its MoQ catalog
and its `LISTING` change.
### The record made true: training, paths, protected content, reader limits, security
- **`int_synth`'s weights are quantised after float training.** SPEC §5 and
[determinism-tiers](extensions/determinism-tiers.md) §5 said the capability decodes
"QAT weights". No committed model was trained that way: the trained corpus vector,
`photo`, was made by `tools/wai_int_synth_real.py`, float training then post-training
quantisation with each layer's shift derived from its measured activation range, and
the weights of the synthetic vectors and of the cross-architecture golden come from a
seed. Both texts now say so, and so do the tools:
`tools/wai_int_synth_qat.py` is marked experimental (it trains against one fixed shift
and made no committed model), and `tools/wai_int_synth_export.py` names the working
path.
- **Stale paths.** The int_synth decoder is `int_synth.rs` at the crate root, not
`neural/int_synth.rs`. The quantum-ops engine and the quantum-sim receipt live in
`wai-quantum`, whose features `wai-rs` forwards. README and SPEC Appendix A no longer
send macOS builders to a `.cargo/config.toml` that no longer exists: `wai-rs/build.rs`
finds libFLAC.
- **Protected content: two lanes** (SPEC §6.5). Lane A, hardware-protected content,
decodes inside the platform's protected media path; WAI's records about it (among
them the served manifest, the segment map, the key-release decisions, the ad
decisions and impressions of `wai.video.ssai`, the program-as-run windows of
`wai.video.channel`, and the content hashes of WAI objects) cover no decoded frame.
This revision registers no `security.encryption` value for it. Lane B, end-to-end
encryption at the application layer (`mls-sframe`), decrypts in the application, so
the tier and the output digests of the sink that holds the key hold as they do for
plaintext; staged-measurement counts no encrypted object, and a verifier without the
key checks none of a `reconstruct-session` claim's rules that need a payload. Lane B
is not a protected-path robustness level. `wai.video.keys` and the confidentiality
extension say which lane they belong to.
- **Where the exact tier holds** (determinism-tiers §5). `decode-equivalence` is shown
on native CPU and on wasm32, on no GPU or NPU; a WebAssembly decoder at the tier uses
no relaxed-SIMD instruction and depends on no NaN's bits; and the tier never covers
output a sink cannot read.
- **Reader limits** (SPEC §2). A reader need accept no manifest longer than 2²⁷ bytes
(128 MiB) or nested deeper than 127, and refuses a longer one from `man_len` alone
(`manifest_too_large`). The length limit is sized to the largest manifest a
conforming staged layer has: a `prev` (staged-delivery §3) naming a different
producer for each of the 2²⁰ units a `WIR1` layer may cover (staged-delivery
Appendix A.3) is about 94 MB. It bounds what a reader reads, not the memory a parse
takes, which in the reference readers is up to nearly 40 times the manifest's length
(`SECURITY.md`). `wai-rs`'s readers, its HTTP head parser (`head_malformed`) and the
Python verifiers apply the length limit, and the HTTP corpus holds it at its edge;
the writers, `wai-web`'s included, refuse to write past it, and refuse a payload whose
length does not fit its `u32` field.
*Envelopes this makes non-conforming:* none of the layout's; an envelope whose manifest
is longer than 128 MiB, or nested deeper than 127, is one a conforming sink may refuse,
so a conforming encoder no longer writes it (SPEC §7).
- **`SECURITY.md`.** How to report a vulnerability, and where the reference
implementation reads untrusted input: the manifest limits and the memory a manifest's
parse takes, the native codec libraries and their gaps (unbounded decompression output
for `wai.text.zstd` and `wai.text.xz`; no fuzzing in CI), module authorisation, and the
fingerprinting risk of `held_sets`. CI checks its section references.
### Vector graphics (Draft): the extension text of `wai.graphics.vector`, §1–§3
- **A new extension, [graphics-vector](extensions/graphics-vector.md) (Draft).**
`wai.graphics.vector` carries titles, credits and other authored graphics as integer
drawing instructions, which the sink rasterises into an overlay layer. This revision
writes §1–§3: overlay-equivalence and text-equivalence, which cover the overlay layer
and the computed text and nothing after them; the `WVG1` payload and the `WOS1`
outline-set format, with their canonical rules, caps and the order in which a sink
refuses, including a per-frame cost each listed size states and a sink verifies at
load; the digest sidecar; and the arithmetic, as the raster stage family RS1–RS9, in
which no value wraps. Outline sets, text and accessibility, and packages follow in
later revisions of the Draft. Nothing is registered: the string is registered once its
conformance corpus is complete.
### wai-web on older engines: the reach builds
- **Three builds from the shipped package, and a loader** ([wai-web/reach](wai-web/reach/README.md)).
`reach/build.sh` derives them from `wai_web_bg.wasm` and `wai_web.js` with no change to
the Rust:
- **L1** legalises the binary's JS interface (`wasm-opt --legalize-js-interface`), so
engines where i64 does not cross as `BigInt` run the full module;
- **L2** lowers it to MVP WebAssembly and legalises it;
- **J** translates the MVP module to ES5 JavaScript with `wasm2js`.
L2 and J come in profiles, kept by `wasm-metadce` from the exports each needs: `full`,
`verify_decode`, `verify` and `receipt`. An ES5 runtime supplies the missing builtins
and joins and splits i64 values at the boundary: a `BigInt` where the engine has one, a
`Number` (exact below 2^53) where it does not. The glue is the shipped glue compiled to
ES5, with counted changes. One of them rebuilds a memory view whenever the memory's
buffer changes, not only when it is detached.
- **`wai-load.js` chooses by feature probes alone.** Tiny modules passed to
`WebAssembly.validate` (MVP, sign-extension, non-trapping float-to-int, bulk memory), one
instantiated module that returns its i64 argument, and checks for typed arrays decide
between shipped, L1, L2 and J. The probes never throw. An early WebAssembly engine line
(WebAssembly and the stack-trace capture API, but no RegExp `dotAll` flag: the feature
`noDotAll`) gets J, or the L2 receipt build, because the full MVP module has crashed an
engine of that line on arm64. A rung can be forced, and the loader says why it chose.
- **Pinned and recorded.** binaryen `version_123` is checked against each platform
archive's SHA-256, and TypeScript, esbuild, acorn and binaryen's WebAssembly build are
pinned by `package-lock.json`; J's `wasm2js` is that WebAssembly build, so J comes out
the same on every host.
`reach/manifest.json` records the SHA-256 and sizes of every output built from the
package the site serves, and the commit that package was built from; `reach/record/`
keeps that package. Given it, the build refuses any output that differs.
- **Checked output for output.**
- `reach/test/conformance.js` (ES5) runs workloads over the repository's corpora on any
rung, and every output must equal the shipped build's. The workloads cover receipts,
the staged corpus, the integer payloads, int_synth, the menu, the output tree and the
i64 boundary.
- `reach/test/run-modern.mjs` forces every rung and profile on a current Node, and runs
the demo verify scripts against each full build.
- CI (`wai-web-reach.yml`) runs a leg per package. On the package of record, kept in
`reach/record/` and held to the manifest's SHA-256, every output must equal the
manifest's; on the tree's own wai-web, the reach builds must still build and pass.
Each leg runs the conformance on the Node images of the engine lines each rung serves
(J on `node:0.12`, the early-engine routing on `node:8.0.0`, L2 on `node:8` and
`node:10`, L1 on `node:12` and `node:14`) and the shipped build on a current Node.
The images stand in for device classes, which the README names by capability. No real
old device has been run.
- **The receipt vector's signer id is a label.** The receipt workloads verify an asset and
its provenance receipt in `reach/test/vectors/provenance/`, signed with `wai_prov`'s
published test seed under its label `did:example:wai-prov`. `wai_prov --carry
<receipt.json>` measures nothing: it carries each step's figure, and its label, from an
earlier receipt for the same lineage and signs them under this revision's id, and it
refuses a receipt that does not verify or whose steps are not this run's. The vector
carries the figures `wai_prov` measured when it first wrote the asset, and the
documented command, carrying the receipt onto itself, writes both files again byte for
byte.
### Generated pictures and scores, made natively and checked by SHA-256
- **The payload of `wai.generate.spectral`** (SPEC §5, *Payload of
`wai.generate.spectral`*). The registration is completed (§8): the payload is an
instruction object of form v0, one canonical JSON object naming the model by its
SHA-256 and giving the prompt, the seed, the steps and every size a sink makes. One
serialization per object, so an envelope's content hash names the instructions; a
sink makes only listed sizes, with that model only, and checks each output by its
SHA-256, never by a 64-bit FNV-1a alone. The intent of an envelope that names the
generator is `create`, and an envelope with another intent is non-conforming. The
model is the generator's parameter set (§3.1) and `model` its identity, so an
envelope that names the generator carries no `prior` pin, and a sink refuses one that
does (§3.1 says so beside the pin rules). `wai inspect` and `wai unwrap` report the
payload, the intent and any pin of such an envelope, ✓ or ✗ with the reason. Vector:
`wai-rs/tests/vectors/generate/instr-v0.json`, the object, the SHA-256 at each of
its four listed sizes, twelve objects at the inclusive bounds a sink accepts (each
with the SHA-256 of its first listed size), and 36 objects a sink refuses, each with
the rule the reference reader finds broken first, which its tests assert.
- **`wai generate`** (feature `generate`) makes a picture with the reference generator
at any size form v0 allows and prints its SHA-256; `--instr` makes a listed size of
an object, read as a sink reads it, and `--write-instr` writes one. `sizes.json` pins
the committed prompt at 256×128 and 640×320. The wasm32 workflow checks that the
browser binding makes every pinned picture and score with the SHA-256 the native
tool wrote (`wai-web/demo/verify_generate_wasm.mjs`), so a page can hold its own
output to these pins. `wai wrap` refuses a `wai.generate.spectral` payload that is not a
form-v0 object, and any intent but `create` over that generator, in every build.
- **`wai score build|render`** (feature `score`). `build` writes a `WSCR` from integer
note fields only (score §2.3); `render` prints `samples`, `pcm_sha256`,
`mixdown_blake3` and `score_hash`, and `--pcm` writes the samples. Every
`score-conformance` vector now states `pcm_sha256`, and `notes/<name>.json` holds it
in the authoring form, which `build` turns back into the committed `WSCR` byte for
byte.
- No library source changed: the generator, the score synth, the world floor and the
browser binding are as they were, so pins made with this tool bind the binding pages
already load.
### Quantum circuits: `wai.quantum.circuit2`, and `wai.quantum.circuit` stated exactly
- **A new capability, `wai.quantum.circuit2`** ([quantum-sim](extensions/quantum-sim.md),
SPEC §5). Magic `WQC2`, contract `ext` `wai.quantum.circuit2/1`, numeric
`wai.det.amp64`, gateset `cliffordT+dyadicP.rn`. The container layout, the gate IR and
the arithmetic are those of `wai.quantum.circuit`; the phase table is not. Each
`P(k)` holds the integers nearest to `2^30·cos(2π/2^k)` and `2^30·sin(2π/2^k)`, so its
controlled-phase ladder gives the QFT. The literal table is normative (§3.2, Appendix
B.2, digest `d34cb793…3510`); an exact-integer interval procedure certifies every entry,
informatively. The contract and the measurement are byte-canonical, and the op-log ends
exactly after its last op. Registered in the machine-readable registry and the
vocabularies, with `wai.det.amp64` as an identifier.
- **`wai.quantum.circuit` is frozen, and its table stated for the first time** (§3.1,
Appendix B.1, digest `3fa9805f…8466`). The text gives the exact recurrence — the sine
formula, the truncating halving, the floor square root — and the deviation of every
entry from the ideal phase: from `P(8)` on the imaginary part is too large, by up to
7032 units of `2^−30`, and `P(19..32)` apply no phase. The claim that this table gives the
QFT is withdrawn. Writers SHOULD NOT emit new circuit/1 content.
- **Both capabilities are specified to the bit** (§2–§4): little-endian integers, section
bounds computed without overflow, a repeated known section refused, the contract and
the measurement read as I-JSON (RFC 7493) by rules stated in full: no repeated member
name, no surrogate or noncharacter, no number of magnitude 2^1024 − 2^970 or more, at
most 127 levels deep, and an integer read exactly; the three refusal
classes (malformed, contract, invalid) in the order a reader checks them, the
complex-product composition, the index convention, the gate matrices, the hash layouts
and the shot sampler. `circuit_hash` covers the canonical contract, so it names the
capability and its table; `statevector_hash` does not. §5 states the error per
capability, and a new §8 why circuit2 is a capability of its own and what an older
reader does with it (it refuses `WQC2` at the magic).
- **Newly non-conforming circuit/1 payloads** (quantum-sim §8, SPEC §5): a contract whose
`ext`, `numeric`, `frac` or `gateset` is missing or carries another value; an
`n_qubits` that is not an unsigned integer of at most 255; a known section kind more
than once; section bounds that overflow; a contract or measurement that repeats a
member name within one object, holds a noncharacter, or holds a number of magnitude
2^1024 − 2^970 or more that an earlier build's float parser let through (some builds
also refused numbers just below it, which are now accepted). No file a reference writer
wrote is among them.
- **Corpus.** `quantum-conformance/` keeps its four original vectors and manifest entries
byte for byte and adds qft5_x22, dyadic_ladder and qft8_ones, which see the phase table:
under either table, `dyadic_ladder`'s statevector hash changes with every ±1 change of
any `P(1..32)` component. `v2/` holds the same seven circuits as circuit2; `reject/` at
least one payload for each refusal a payload can carry, with its class; and `accept/`
payloads at the edges of the same rules that a reader must accept, with how each reads.
`tools/referee.py`, integers only, derives both tables, requires Appendix B to state
them, reads every JSON section by the same rules, and rebuilds and checks every file. The
Appendix B tables are rendered from the reference constants by
`wai_quantum_vectors appendix`, and a test fails when the text differs.
### wai-quantum 0.4.0: circuits carry their gateset
- **`Gateset { V1, V2 }`** names the capability a circuit is reconstructed with, and
`Circuit` carries one in a new field. `Circuit::new` and `Circuit::qft` build V2
(`wai.quantum.circuit2`); a circuit read from a container takes the container's, and one
derived from another keeps it. `PHASE_V1` and `PHASE_V2` are literal constants: no
recurrence runs at load. `simulate`, `to_wqc`, `circuit_hash`, receipts, the fabric's
lanes and the toolchain follow the circuit's gateset. `from_wqc` reads both magics,
checks a circuit/1 contract's values and a circuit2 contract's bytes, reads the contract
and the measurement as I-JSON with a strict reader of its own that decides every rule
from the text, the number range on the literal's digits (so neither a build feature nor
a float parser's limits changes a verdict), refuses repeated sections and
wrapping bounds, and no longer sizes an allocation from a claimed op count.
`reject_corpus()` and `accept_corpus()` return the payloads a reader must refuse, each
with its class, and must accept, each with its reading; the corpus's `reject/` and
`accept/` trees are written from them.
- **Every circuit built in code changes its `circuit_hash`**, since the contract names the
capability, and its statevector wherever `H`, `T`, `T†`, `P(3)` or `P(k ≥ 5)` acts on a
nonzero amplitude (`P(1)`, `P(2)` and `P(4)` are the same in both tables). Every
circuit/1 artefact reproduces: the four original vectors, the legacy receipt pins, the
lanes32 certificate corpus and the other pins are unchanged, built in `Gateset::V1`, and
new V2 pins sit beside them.
- **wai-rs** registers `wai.quantum.circuit2`; `quantum_envelope` tags an envelope with the
circuit's capability and refuses one whose capability and magic disagree. The
`wai-quantum` CLI takes `--gateset v1|v2` (default v2) and names the capability in its
JSON; with `--gateset v1` and a phase of `k ≥ 8` it prints the circuit/1 deviation on
standard error. `wai_quantum_meter` seals the capability the file names. The browser
sink reads both magics; the IDE has `set_gateset`, and its `add()` sets the parameter of
every gate but `P` to 0. The serve component, `wai-quantum-serve` 0.5.0, takes
`gateset=v1|v2` on `/run`, `/qasm` and `/receipt`.
- **Migration from 0.3** (a breaking release):
- `Circuit::new` is V2. To reproduce or verify a 0.3 artefact, read its `WQC1` file, or
set `c.gateset = Gateset::V1` on a circuit built in code; a receipt's `gateset_of`
says which gateset a circuit's ops match it under.
- `quantum::phase`, `quantum::inv_sqrt2` and `quantum::CAPABILITY` are removed in favour
of `Gateset::phase`, `Gateset::inv_sqrt2` and `Gateset::capability`.
- `to_wqc`, `circuit_hash` and `quantum_toolchain::circuit_id` return a `Result`; they
fail only on a circuit its gateset does not allow, never on one that has simulated.
- `Circuit` and `QuantumError` are `#[non_exhaustive]`; `QuantumError` gains `Contract`
and `kind()`.
- `quantum_fabric::certify_lanes` and `lanes::random_circuit` take a gateset, with a
kernel and generator id per gateset; the circuit/1 ids are unchanged.
- `validate` refuses a gate with more than 255 controls, and in V2 repeated controls and
a parameter on any gate but `P`. `from_wqc` refuses the payloads quantum-sim §8 lists.
### Integer operators (Draft), and the payload text of two integer codecs
- **A new extension, [integer-operators](extensions/integer-operators.md) (Draft).**
`wai-ops/1` is the integer operator set the integer-exact capabilities decode
through: each operator's exact arithmetic over the integers, with rounding, bias
placement and shift scope as parameters of requantisation; a range policy on every
tensor (`derived`, `clamp` or `check`); the `acc64` and `acc32` accumulator profiles;
static bounds rules, saturating at `±2^255` so that a 512-bit integer holds every
value they form, and a canonical bounds certificate that decides which profile a
parameter set meets; the accelerator subset `wai-ops/1-npu`; the `WHG1` graph form;
the stage operators of the drivers, among them the mANS decode of
`wai.image.jpegai` (E8) with its scale path (§13.6.1); and canonical lowerings of
the existing network forms (`int_synth.json`, `WHM1`, `WAM1`, `WMB1`) to `WHG1`.
The reference crate, the tool and the conformance corpus it names follow this
Draft; until they land, each capability's reference is the decoder its payload
extension names.
- **[integer-payloads](extensions/integer-payloads.md) writes down two more formats.**
§13 is the `wai.neural.int_synth` payload and its set, `int_synth.json`; §14 is the
`WIM1` payload and the `WMB1` bundle of `wai.neural.int_mlicpp`, including that the
entropy network reads every element of `y` not yet decoded as 0, and that the three
networks and the tables share one `afrac` in `1..=30`. §9 gains the members and
checks of `entropy.json`, and states that the layers' arithmetic is
integer-operators §13.2 and §13.3. No decoder changes: §13 lists the
`wai.neural.int_synth` checks the reference decoder makes once it decodes through
its lowering.
### Staged measurement and the session claim in the browser
- **The measurement core, bound for JS** ([staged-measurement](extensions/staged-measurement.md)).
`wai-web` binds `wai::stage_measure` (`src/measure.rs`), the code `wai_stage_measure`
runs, compiled to wasm32: `evaluateTitle` gives the text `eval --json` prints for a
title (counted bytes, quality integers, first presentable bytes, the layering
overhead's points, deduplication, the output tree), `simulate` and `simulateModel` the
text `sim` prints, `countObject` one envelope's counted bytes or the code it is not
countable with, `qualityRgb8` one picture pair's integers, and `outputRoot` the output
tree's root. A title is read from a map of files; `titleMissing` names the next file it
needs, so a page fetches them one at a time. Sums of squares are decimal strings, as the
corpus writes them.
- **The session claim, sealed in the browser** ([jwp-receipts](extensions/jwp-receipts.md)
§2.4.12). The claim classes (`receipt_claims`) build for wasm32 with the measurement
core, and CI builds them so. `sessionBegin` records a session at its entry object
(`wai::session_recorder::SessionRecorder`): the decoder that decoded each envelope, each
presentation with its output's digest, each object received. `finish` seals the claim
with a key whose seed the page supplies, checked against the record first; a browser
session's `energy` is `null`. `verifyClaim` checks a claim against the presentations and
the objects as the claim-conformance runner does.
- **Proved byte for byte.** `demo/verify_measure.sh`, run in the wasm job, requires every
golden, refusal title, counted object, quality pair and simulator result of
`staged-measure-conformance/` from the wasm32 build, and the native `eval --json` of every
fixture to equal the wasm32 evaluation. A session recorded the way a page records one is
the native tool's claim byte for byte and commits to the golden output tree; the wasm32,
native and Python verifiers give the same verdict on it and on its altered variants.
- **A recorder that holds the envelopes checks the claim against them.** A sink hands
`SessionRecorder` each object's bytes (`received_envelope`): the recorder derives its
content hash, counted and transferred bytes, and whether an entry names it, and
`seal_checked` checks the claim against every envelope it holds, as a verifier holding
the `envelopes` context does, so an entry naming a capability its envelope does not give
never leaves the sink (`session_source_mismatch`). An object the sink does not hold is
`received_unheld`, by its content hash; the recorder no longer takes an object's figures
as told. The native tool's session over a title and the browser's `SessionHandle` both
hold their envelopes; `decoderFor` refuses a capability that is neither the envelope's
nor its fallback and requires the decoder's `first_unit`, `received` takes the bytes
alone — bytes that are not an envelope included, recorded as an inert object neither
counted nor presented, so the session accounts for every byte transferred —
`verifyClaim` takes the envelopes, and integers from JS are refused unless whole and in
range.
- **An object is listed once, as its envelope gives it** (§2.4.12, the objects). No
(content hash, path) is listed twice (`session_object_duplicate`): a sink that fetches
an object again lists it once, its transferred bytes every byte of every fetch, as the
recorder merges repeats. After the sums, an object is `presented` exactly when a decoder
entry names it (`session_presented_mismatch`). With the envelopes, an object listed with
a held envelope's content hash states that envelope's counted bytes, `null` exactly when
it is not countable (`session_counted_mismatch`).
- **Envelopes no entry names are received, not refused.** Inert bytes — of neither
magic, or whose `security` member is not a security object — are refused as unreadable
only where a decoder entry names them (`session_envelope_unreadable`); those, and an
encrypted envelope, no entry names are a received object, uncounted and not presented,
and one listed with counted bytes, even 0, is refused (`session_counted_mismatch`). A
page that receives such bytes lists them and seals its session.
- **The layers and units an envelope gives.** A decoder entry states `first_unit`, the
unit at which its object's first unit is presented. With the outputs and the
envelopes, each presentation through an entry whose envelope is held is at the layer
count that envelope gives — 1 for a base or a rendition with no `refines`,
`refines.layer + 1` for a layer (`session_layers_mismatch`) — and of a unit it covers:
from `first_unit` plus the span's start, the span's count of units, or the base's units
where a layer has no span or the object is a base (`session_unit_outside_object`). A
re-decode decodes unit `unit − first_unit` of the entry's object. A base's units are read
in its payload's own format — of its manifest capability's and its fallback's pinned
formats, the one whose magic the payload starts with — whichever capability the entry
names. `claim_verify.py` reads
the units from the integer payloads as `wai-rs` does, and `differential.py` adds random
sessions holding a base's and its layers' envelopes, as `WAI1` envelopes and as `WAI2`
components.
- **A layer is one a sink can apply, at its base's first unit** (§2.4.12). An entry naming
a held layer a sink refuses is refused (`session_layer_not_applicable`): not a
refinement or with a fallback, its `refines` malformed or absent, a `WAI2` refinement
component that does not conform (SPEC §7.1), its base a layer, not admitted by the
layer's capability or decoded only through its fallback, its span or runs outside a
held base's units, or its payload not `WIR1` with a unit for each it covers
(staged-delivery §4.1 steps 1–4 and 6). A `WAI2` component and its exploded envelope
are one base in either form (staged-delivery §9). A held layer's entry states the
`first_unit` of an entry it is applied over — naming its base through its own
capability — and an entry naming a `stage` claim's base or a layer it lists states the
unit `stage_sets` names that claim at (`session_first_unit_mismatch`).
- **An entry names what a sink dispatches** (§2.4.12 rule 4): never a `WAI2` companion,
a rendition or refinement whose payload does not match its `sha256`, a capability that
is not dispatchable, or the fallback of a `WAI1` whose capability is companion-only
(`session_source_mismatch`).
- **One verdict for either name, by capability, and for a key holder** (§2.4.12). An entry
naming a `WAI2` rendition by its exploded envelope's content hash, with only the `WAI2`
held, is read as naming the rendition by rules 4–8; the exploded envelope's own bytes,
held, are a separate delivery. A manifest or rendition is a layer only when its
capability is refinement-only: a base carrying a stray `refines` is a base, as
dispatch and selection already treat it (SPEC §4, staged-delivery §3), and `wai stage
apply` now routes by capability. An encrypted envelope an entry names is accepted —
a sink holding its key decrypts and dispatches it (SPEC §6.4 step 3) — and what the
entry states of it is the signer's statement.
- **A WAI2 offers its refinements only when every component conforms** (SPEC §7.1,
*Selection*). The text now states the reference sink's rule: an envelope one of whose
components breaks a rule of §7.1 or §5 (`check_component_roles`, now on `ManifestV2`)
offers none of its refinements, and may still present the rendition it picked. `wai
stage explode` follows it, refusing to explode a component of such an envelope that is
of a refinement-only capability or marked a refinement; rule 5 matches it
for the whole envelope; and http-delivery §7 step 4 states it for a client planning
from a verified head: each refinement of a nonconforming envelope is dropped
(`components_nonconforming`), never requested, one stating no `sha256` dropped for that
too, on the range path and the whole-object path alike. A refinement is one whose
`refines.base`, read on its own, is the pick's id (`container::refinements_naming`), so
one whose `refines` is otherwise malformed is reported too; of a conforming envelope
that is exactly the refinements whose `refines` is well formed. The HTTP corpus adds
ONC's conforming twin, OCF, whose refinement is fetched and delivered on both paths,
and ONM and ONX, a `refines` of the pick's id alone (reported) and of no entry's (not
reported); `OR`'s refinement, which names the pick, is now reported. `falsify/` adds
`conforming-withheld` and `refines-read-whole`, each with its `http_verify.py
--mutate`.
- **An encrypted envelope's clear parts are checked** (§2.4.12). SPEC §6.4 encrypts
payloads only: rules 4–9 now read an encrypted envelope's manifest and rendition table
as any held envelope's — the rendition an entry names, its capability, the component
rules, a base's layer count, a companion-only capability, a fallback, and the classical
length its table declares — and leave to the signer only what needs a payload: whether
a payload matches its `sha256`, a base's units and a layer's `WIR1` payload. A `sha256`
that is not 64 lowercase hex digits matches no payload, so an entry naming its rendition
is refused, encrypted or not (rule 4, `session_source_mismatch`).
- **The recorder states the classical length.** `SessionRecorder` derives
`classical_declared` at the tally from the `WAI2` envelopes it holds, readable or
encrypted, exactly as rule 9 counts them (`claim_receipt::session::classical_length`,
which rule 9 calls too); `SessionRecorder::classical`, which a sink called by hand and
the browser binding never could, is removed. A page that receives a `WAI2` it does not
decode now seals its session. `verify_measure` seals two such sessions in wasm32 — over
`counting/wai2-classical.wai`, whose second rendition is PNG, and its encrypted twin,
both new in the counting set — checks the length against the rendition table, and the
native and Python verifiers give the same verdicts. A limitation of this revision: the
length comes from the `WAI2` envelopes held in full, so a sink that received a `WAI2`
only as byte ranges, holding its head but not the envelope, cannot yet state that
envelope's classical rendition (§2.4.12, `classical_declared` and rule 9). Recording
such a session is HTTP session work still to come.
- **A record or a derivation is no component in any role** (SPEC §7.1, *Component
identity*; `record-or-derivation-as-component`).
- **A record or a derivation is no companion** (SPEC §7.1, rule 1). A companion's
capability is companion-only or unregistered; a capability registered in another
kind — a payload, a record or a derivation — is refused
(`standalone-as-companion`), which the reference now reads from the registry rather
than from its list of media capabilities, which left records and derivations out.
- **Every refusal accounted for.** The claim-conformance README's table lists each
refusal a sink makes that decides whether a presentation can happen — staged-delivery
§3, §4.1, §4.3–§4.5, §9; SPEC §4, §5, §7.1 — with the §2.4.12 rule and vector that
refuse it, or the sentence of §2.4.12 that leaves it to the signer's statement.
- **The browser claim is a release build.** `verify_measure_wasm.mjs` requires the wasm32
claim's `host.build.profile` to be `release` and its target `wasm32-unknown-unknown`.
- **The build profile is the optimisation level.** `release` is a build at `opt-level`
2, 3, `s` or `z`, whatever its profile is named, and `debug` one at 0 or 1
([jwp-receipts](extensions/jwp-receipts.md) §2.4.8's `build`): a WebAssembly build
optimised for size is `release`.
### One Ed25519 rule for every signature
- **SPEC §7 *Signatures*.** Every Ed25519 signature WAI defines is verified strictly: the
key is its point's one canonical encoding and not of small order, `S` < L, `R` is not of
small order, and the cofactorless equation holds with `R` compared byte for byte. The
receipt extensions cite the rule instead of restating it; jwp-receipts §3.1 step 3
keeps its two reasons, `bad_signer_key` for steps 1 and 2 and
`signature_verify_failed` for the rest. OER/2's chain signature keeps the rule
oer-receipts §5 settles.
- **One verifier in wai-rs.** `wai::sig::verify_strict` is the only Ed25519 check written
in the crate but OER/2's. The video, SSAI, key-release, channel and package receipts
(both versions), the provenance, ternary, spatial-audio, volumetric, film, score and
haptics receipts, and a world's session receipt, ops and seal moved onto it; before,
each read its key with the library's lenient decoder and checked the lenient
equation, which accepts a key of small order (under the identity, `(R, S) = (B, 1)`
verifies over every message), a second spelling of a key, and an `R` of small order.
Claims, JWP groups and the key log, which held the rule in a copy of their own, call
it too. It builds under the `ed25519` feature, the dependency alone.
- **One verifier in Python.** The claim, JWP, signer, HTTP and packaging verifiers share
`claim-conformance/_ed25519.py`, the rule written from RFC 8032 with the standard
library. `pack_verify.py` had checked signatures leniently.
- **Corpora.** Each receipt family's corpus carries the three signatures a verifier
checking only the equation accepts — the identity as the key with `(R, S) = (B, 1)`,
the identity spelled `y` = p + 1, and an `R` of small order under the base point —
and every conforming verifier refuses them: new cases in jwp-conformance,
signer-conformance and pack-conformance (`mutations/` and `receipts/`); claim- and
http-conformance had them. No existing vector's expectation changed.
### The int_codec model and vectors: trained and coded on a generated signal
- The int_codec model and vectors are now trained and coded on a generated signal;
earlier revisions used a third-party recording.
- `tools/wai_int_audio_export.py` reads no audio file. It writes its training signal
and the clip it codes from a seed (16 kHz mono: tones, chirps, noise bursts and
speech-like formant sweeps), in integer arithmetic, so a seed gives the same samples
on every machine; `--signal-only` writes one with the Python standard library alone.
Training is seeded from the same seed with deterministic algorithms and a fixed
thread count: two runs gave byte-identical files. The seed and the command are in
[`wai-rs/tests/vectors/int_audio/README.md`](wai-rs/tests/vectors/int_audio/README.md).
- Regenerated through their generators: `wai-rs/tests/vectors/int_audio/` (the model,
both bitstream forms, `audio.json`, `sha256.json`); `int-payload-conformance`'s
`vectors.json` and its thirteen `wia1_*` refusal fixtures; the staged-delivery case
`audio_int_codec_1l` and `index.json`; the staged-measurement audio title,
`goldens.json` and `sim/results.json`. The `WIA1` format, the capability and the
model's shape are unchanged. `wai_int_payload_vectors` now reads the place of a
`WIA1`'s first table from the payload's header for the two fixtures that change it, so
they keep their meaning on tables of another length (on the earlier vector the
offsets are the same).
- `tools/wai_audio_provenance_check.py`, run by CI: every committed audio vector is read
to PCM and correlated, at every lag, with the reference recordings the CI host has;
above 0.5 it fails, and a host with none skips it and says so.
- Every corpus states its content's provenance: a `**Provenance.**` line in each
`*-conformance/README.md` (OER/2's settled corpus aside) and a row per directory in
`wai-rs/tests/vectors/README.md`, required by `tests/corpus_provenance.rs`.
CONTRIBUTING states the rule. Two vectors are flagged there as not generated and to
be replaced: `int_video`'s `video_real` and `neural-conformance`'s `photo`.
### Staged tracks over MoQ: a track per layer, a receipts track per track, delivery timeouts per hop, chains that recover
- **Re-read against the drafts.** [moq-streaming-format](extensions/moq-streaming-format.md)
names the revisions it was last read against, `draft-ietf-moq-transport-22`,
`draft-ietf-moq-msf-01` and `draft-ietf-moq-cmsf-01`, and cites their sections. The
re-read corrected: MSF's packaging values (`catalog` is a track name, not a value); what
orders delivery (MOQT, subscriber priority first, then publisher priority, MOQT
§5.1.2); how properties are carried (MOQT's track and object properties, of which MSF
defines one); `SUBGROUP_DELIVERY_TIMEOUT`, which runs from the subgroup's FIN to its
acknowledgement, not from an object's arrival; that MSF maps every object to a stream
of its own, so a staged object never travels as a datagram; and that MOQT-22 replaced
the Joining FETCH with fill fetch streams.
- **§4.1 Staged tracks.** A stage set is carried one track per layer: base tracks and a
track for each layer index, each object one whole envelope. Each entry carries
`wai_staged` (`set`, `layer`, `lead_ms`). A set's extent is its tracks, layers 0 to the
highest with a track at each, so an MSF delta update that adds, removes or clones a
track changes it alone; `lead_ms` is read only while the set is live. Tracks are named
by namespace and name (a namespace inherited as MSF says), unique in a catalog, one
namespace to a set; the check needs the catalog track's namespace, and a checker that
does not know it refuses a set that mixes declared and inherited namespaces; `renderGroup` is an integer that fits 64 unsigned bits and
`depends` an array of strings, each naming a track of the catalog in the entry's own
namespace; a layer track depends on a track of every lower layer and on none of its
own or above. A sink checks a set's entries in a fixed order and reports the first
failure (`staged-malformed`, `staged-set-incomplete`, `staged-depends`,
`staged-grouping`, `staged-lead`, `staged-receipts`), treating a failing set's layer
tracks as absent. On a live track each group carries one object in a subgroup of its
own. The base track's publisher priority is first among the set's (lower numbers first),
though a subscriber's own priority comes before it. A layer object's
`OBJECT_DELIVERY_TIMEOUT` is at most its slack to the last unit it covers, settable
per object; a base's never shorter than the time to its last unit.
Timeouts run per hop and never decide lateness; a sink's longer target latency cannot
extend a publisher's timeout. A subscriber caps its stage, and its receipts, by
subscription.
- **Receipts.** `wai_receipt_alias` (§4) is the `track_alias` a track's receipts sign:
MOQT's Track Alias is chosen per session by each publisher on the path, so it is not
that number past the first hop. Every alias in a catalog is an integer, compared as a
number. `wai_receipts_track` names each media track's own **receipts track**
(`wai-receipts`, at the media track's priority, no delivery timeout), whose group *g*
carries the receipts of the media track's group *g*. No two tracks share an alias or a
receipts track: a rendition of a layer, a clone among them, overrides both and has a
chain of its own. Each receipt says which MOQT object it is about — `group_id` the
MOQT Group ID, `object_id` the Object ID, the publisher's key and the track's alias —
and a subscriber refuses one that does not (`moqt_mismatch`, a new jwp-receipts §3.3
reason) before verifying it, holding nothing of it. A subscriber sets no delivery
timeout on its subscription to a receipts track. A MOQT relay that carries receipts as
opaque payload is not a jwp-receipts §4 relay; the subscriber verifies every receipt
(jwp-receipts §4.1 amended).
- **Recovery.** A missing receipt is recovered by FETCH of its receipts-track group. A
subscriber states its bound in counts: receipts held live for each chain (a time only
beside it, never alone), held as history for each chain, and held across its chains.
Past the live count the held receipts are refused (`parent_not_recovered`, a new
jwp-receipts §3.3 reason) with their layers, the chain re-anchors at its next validly
signed group, and the layers of the groups the anchor skips are refused. A subscriber
that joins mid-stream declares a join and anchors at its first group. Each anchor
leaves a gap, which a group linking to either end fills as history, in any order — the
ascending order of a FETCH or a fill fetch stream included — and never as a fork;
history waits under its own count. A group admitted before, offered again with the
same bytes (MOQT can deliver one object twice, by a subscription and a fill), changes
nothing (`accept again`), never `chain_break`, and its objects verify against it as
against the head. jwp-receipts §3 step 3 defines holding,
re-anchoring, joining and history, and says what an anchor does not prove; §6.1 says
how anchors are reported and why the bounds hold across chains.
- **Other text.** staged-delivery §8, §9 and §13; jwp-receipts §4.4 and §5.9;
http-delivery §10.2 and Appendix C. The registry lists `wai_staged`,
`wai_receipt_alias`, `wai_receipts_track` and `wai-receipts` as wire identifiers.
- **Reference implementation.** `staged::moq` (feature `staged`): `StagedTrack`,
`SetTrack::to_catalog_json`, `receipts_track_json`, `check_catalog` and
`check_catalog_in`, `depends_for`, `live_object_rules_ok`, `priorities_ok`,
`effective_timeout_ms`, `expired_at_hop`, `layer_timeout_ms`, `base_timeout_floor_ms`.
`jwp_delivery::ReceiptChains` gains `with_hold`, `with_join`, `admit`, `expire`,
`take_events` and `anchors`, and for MoQ `for_moq` (every count at most
`MAX_HELD_GROUPS`) and `admit_on`; `HoldBound` states live, history and total counts
(`from_json` refuses a statement without them); `ObjectReceipt::check_moqt`. Each
group's signature is verified once, and a held group is offered again only when a
group it links to is admitted (`verifications`, `reoffers`, `held_total` count them).
Without recovery it behaves as before. `tests/staged_moq.rs` runs the corpus trace
with signed receipts through `for_moq` under each subscriber's stated bound,
per-track chains, dropped objects checked by inclusion and named by the relay's
`delivery-gap` claims, and sealed `stage` claims.
- **Corpora.** `staged-conformance/moq/`: the set's MSF catalog; catalogs around §4.1's
check, among them type cases, namespace inheritance under the catalog track's
namespace, another and none known, MSF delta updates (a clone of a layer refused
unless it overrides its alias and receipts track) and conversion to on demand; and a
trace of the set through one relay to four subscribers, two layer objects dropped on
timeout and one forwarded within its timeout and late, one subscriber's receipt lost,
recovered by re-anchoring and the gap then fetched and filled as history, and one
subscriber joining mid-stream. `rate_port.json`:
the `rate` predictor beside the f64 interpolation of the layered-PCM ladder idea,
equal wherever that form is exact, differing (pinned) where it rounds next to a half.
`jwp-conformance/` gains recovery vectors — among them a gap fetched in ascending
order after a re-anchor, a joiner's ascending fill under a live bound of 4, the
history bound passed, the bound across chains, and a group delivered twice — appended
after every existing one. `staged_verify.py` and `jwp_verify.py` check all of it independently;
`chain_gap_differential.py` lets both chain verifiers recover.
- **joule-moq** (`joule-code/`): `staged` (the `wai_staged` member, the layer read from the
envelope, the prototype `mrl_*` header compared and never obeyed), `ReceiptChains` per
key and track, verifying each group's signature itself and knowing a group admitted
before by its bytes, with the same recovery and bounds, and a `MockRelay` that forwards staged tracks on
a caller-driven clock: per-hop timeouts, capping by subscription, one object to a live
group, a receipts track per media track, `RelayStats` keyed by track, group and object,
and send times past the clock refused. Its test `staged_tracks_drop_on_timeout_keep_receipts`
runs the same trace.
- No existing envelope, receipt or claim byte changes. Two existing jwp-conformance
vectors change their expected outcome: an exact replay of a group the chain admitted
(`chain_head_predecessor_replayed`, and g1 in `chain_legacy_replay_loop`) is now
`accept again`, not `chain_break`.
### The session claim: what a sink reconstructed
- **The `reconstruct-session` class** ([jwp-receipts](extensions/jwp-receipts.md)
§2.4.12, the next number after `stage` and `delivery-gap`): a sink's statement of one
session, about its entry object — the decoders it used, on which host and against
which parameter sets; the path each unit took and why it switched; the presentations
it made after their deadline; a commitment to every output it presented, by the root
and leaf count of [staged-measurement](extensions/staged-measurement.md)'s output tree;
the bytes it counted and received, and the objects it could not count; the classical
rendition it did not need, by its `WAI2` table length; the `stage` claims it signed
about the stage sets it observed; and, where metered, the decode figure of one
completion with its acquisition class (`null` when unmetered, never 0). Its rules at
§3.1 step 4, and step 6 against the presentations (the tree, first-use order of the
decoder entries, paths, units, missed presentations, and re-decodes through decoders
whose replicate class makes them byte-exact), the objects received (its entry object
and every decoder entry's object among them), the envelopes received and the `stage`
claims it names. Each rule
and check has its reason in §3.1's table. Its capped lists are exact: the switches and
the missed presentations are each listed in full up to the list's cap, and counted in
full.
- **It restates no `stage` claim.** A `stage` claim states one set's layer objects, its
units at each stage on time and its delivered stage; the session names it by link hash
with the title's unit that is the set's unit 0, and commits to every output across the
title, late ones included. A sink signs one for each epoch of a set — before it forgets
the set's base, and at the end — and names them all. Step 6 binds the two both ways:
each set's units were presented; the session presented, at each stage, no fewer units
than a `stage` claim counts; no unit at a stage beyond the layers that the claims whose
sets hold it — every epoch's, and every base's over the unit — list as `applied` or
`partial` (a `late` or `refused` layer applied to no unit); and each claim's base and
arrived objects are among the objects the session received. Nothing binds a `stage`
claim to a session beyond its base and key: one an earlier session of the title signed
can be named.
- **A decoder entry names the object it decoded**, and the envelope binds it. Its
`object` is the content hash of the `WAI2` envelope whose rendition it decoded, or of
the `WAI1` envelope; it is among the objects received (`session_source_not_received`).
The envelopes a verifier holds — every `WAI2` envelope received, and any `WAI1` one —
are among the objects received, read as plaintext envelopes
(`session_envelope_unreadable`), and an entry whose object is one of them names what it
gives: a rendition and that rendition's capability, or the `WAI1` manifest's capability
or fallback (`session_source_mismatch`). A signer cannot relabel an entry whose envelope
the verifier holds out of the re-decode check, by any route; which object an output came
from remains its statement. An entry decodes one object, so a title's output-tree
entries are (capability, pin, object) as well (staged-measurement Appendix C), and a
session over a title still commits to the title's tree.
- **The classical rendition.** An envelope's classical rendition is the first selectable
one whose capability the codec glossary links from an entry of era `classical` and
whose media is the envelope's; WAI's own formats, neural codecs, records, parameter sets
and text never qualify.
- **Authorities** (§7.5): a session needs `reconstruct-session`, not pinned-only. Its
energy figure is a second item for confirmation, needing `reconstruct-session` and the
energy authority of its class; a verifier reports the two side by side, so an origin
can stand behind a session and not behind its figure. A session's subject's origin is
not expected for it: a relying party names the origin it expects, if any.
- **Figure profiles** ([energy-measurement](extensions/energy-measurement.md) §6.1): the
`moq-jwp:claim-link` row widens to a session whose `energy` is not `null`, so a
`measurement` claim publishes the bracket behind a session's figure.
- **Delivery telemetry**'s `claim` member names a registered class now
([delivery-telemetry](extensions/delivery-telemetry.md) §2.1).
- **staged-measurement §8.6–§8.7**: a tool probes a power reader's rail at rest before it
brackets a decode — absent, stale or live by energy-measurement §3.1's rule — and reads
an energy counter as power by its successive differences across its wrap, ending the
samples at an interval that cannot be a reading — one implying more power than a
processor rail draws, one longer than ten sampling intervals, or a difference of more
than half the counter's range (a counter reset); a tool that signs a session claim
seals a figure only where its verdict is clean, beside the `measurement` claim on it,
its burst run on a power of two of workers, which the session's host states as its
`threads`, so the claim names no more of the machine than the session
(jwp-receipts §6.7).
- **Reference implementation.** `claim_receipt::session` (the class),
`session_recorder` (a sink's tally, each decoder named with the object it decodes; it
verifies each `stage` claim as it records it — one for each epoch of a set — requires
it signed by the key that finishes the session, and checks the sealed session against
what it recorded and against those claims before it is returned),
`key_log::Item::figure_of_claim`, `energy_report::rail_liveness`,
`energy_burst::counter_power_samples` and `rapl_power_reader`,
`energy_burst::{measure_on, power_of_two_workers}`, `stage_measure::run::completion`,
`stage_measure::decode::{steps, prepare}` and `stage_measure::session`, and
`wai_stage_measure bench`, which times each case of a title — one base's pinned decode
or one layer's offer, its checks and hashing after the clock — over rotating rounds,
compares every run's outputs with the evaluation's and aborts on a difference, records
each run and the operator's statement of whether the machine was idle, meters the
decode where a live rail allows, and with `--sign` seals the title's session and the
report on its figure. A lib test replays every `staged-conformance/` case as a sink and
requires the session it records, naming each epoch's `stage` claim, to seal. The
output tree builds under `receipt_claims`; the switch reasons live in the ungated
`delivery_terms`, which `telemetry` re-exports, so `receipt_claims` does not take
`telemetry`.
- **Corpora.** `claim-conformance/` gains the session vectors (§5.11) — every reason the
class adds, output trees of 1 to 33 leaves, every context, the classical rule across
the registry's kinds of capability, the caps at their edges, a base forgotten and
decoded again (`staged-conformance/`'s `record_budget`, its envelopes and each epoch's
`stage` claim) and every route by which an entry could be relabelled — and
`signer-conformance/` the session's authority and its figure's; `claim_verify.py`,
`signer_verify.py` and `differential.py` read the class. `claim_verify.py` reads
envelopes with `staged-conformance`'s strict readers, and `differential.py` compares the
two implementations across both stage bounds and many epochs, the caps, malformed and
encrypted `WAI1` and `WAI2` envelopes, entries naming their objects or not, and every
registered capability as a classical candidate. Every vector already in both corpora is
unchanged, and the claim corpus pins the vectors before the class by a digest computed
before it was added. `staged-measure-conformance/goldens.json` restates each title's
decoder entries with the envelope each decoded, and so its output root.
- **Wire.** No existing receipt, claim, pin or corpus vector changes bytes; a verifier of
an earlier revision refuses the new class as `unknown_class`. A title's output root
under staged-measurement Appendix C changes, its entries now naming their envelopes.
### Stage receipts: chains per key and track, objects verified by inclusion, `stage` and `delivery-gap` claims
- **One receipt chain per publisher key and track** ([jwp-receipts](extensions/jwp-receipts.md)
§2.4.1, §3 step 3, §6.1). `group_id` and `parent_receipt_hash` run per
(`signer_pubkey`, `track_alias`). The text disagreed with itself: the signing
payload's comment and §6.1's last bullet said per track, §2.4.1 and §6.1's first
bullets per key; all now say per key and track. Receipts issued with `track_alias`
`0`, or chained per track, verify as before. Receipts a publisher chained per key
across non-zero tracks, as the earlier §2.4.1 allowed, now fail at §3 step 3: each
group whose parent is on another track names nothing its own chain holds
(`parent_not_held`). A stage set carries each layer on a
track of its own, and a subscriber verifies the tracks it subscribes to without the
receipts of the ones it does not. A group that passes §3 steps 1–3 can head its chain
whether or not its objects arrived, so a dropped object does not break its track's
next link. §6.1's checks are now real in the reference verifier
(`jwp_delivery::ReceiptChains`): the monotonic `group_id` guard per key and track
(`group_id_not_increasing`) and the freshness bound on `group_end`
(`group_not_fresh`), off by default for labelled groups.
- **A legacy link authenticates nothing** (§3 step 3, §6.1). The legacy payload signs
neither `parent_receipt_hash` nor, through the link hash, anything that covers it, so
a relay that rewrote one link — the first group's, to name the head — made the
chain's whole history link again as new. The legacy payload does sign `group_id`, so a
verifier now refuses a legacy group whose `group_id` does not exceed its chain head's,
whatever its opt-in setting. A labelled replay fails its signature.
- **Forks are told from gaps** (§3 step 3). With a chain's head held, a `null` parent or a
parent that is another group the verifier holds is `chain_break`; a parent naming
nothing it holds is `parent_not_held`. A chain's live edge is the highest `group_id`
of a group that verified and arrived inside the freshness window, linked or not; a
group whose signed `group_id` is below it is history, exempt from the bound, so a
group a relay delayed past the window no longer wedges the chain behind it, and a gap
of any length fills in any order. Both payloads sign `group_id`, so no rewritten link
earns the exemption. A previous group of another key or track
is not held: a `null` parent beside one starts the chain, any other parent is
`parent_not_held`. `group_chain_break` in `jwp-conformance/` now holds a second chain
head; its earlier input, a parent that is not the previous group held, is
`group_parent_not_the_previous` and is `parent_not_held`.
- **`inclusion-verified`** (§3, *Verified by inclusion*; §4.1, §4.4): an object receipt
checked without the object's bytes — steps 1, 2, 3 and 5 — establishes that the
publisher signed an object with that content hash at that position, and is never
acceptance or delivery. A relay forwards the receipts of every object it does not
forward, whatever the reason, and **MAY** say why in a `delivery-gap` claim (§4.2).
`ObjectReceipt::verify_against(group, envelope: Option<&[u8]>)` returns
`Evidence::{DeliveredVerified, InclusionVerified}`, which converts into staged
delivery's `ReceiptEvidence`.
- **The `stage` class** (§2.4.10): what a party observed of a stage set, about its base —
the stage requested, committed, delivered and presented, as points of staged
delivery's vocabulary `urn:wai:staged-delivery:1` (registered in
[energy-binding](extensions/energy-binding.md) §7.2; §2.4.9's vocabulary row names
it); units by stage; the tier and stage hash of what was presented; every layer
object with its units, bytes, status, evidence and staged-delivery §4.2 reason;
conflicts, deadline misses and envelope bytes. Six rules at §3.1 step 4, in order:
the points; the stage hash; the units; the objects (in order and within the set,
except an object refused before its span was checked; evidence `delivered-verified`
for an applied layer wherever a delivered stage is stated; bytes for every object
that arrived and none for a dropped one, fewer in all than the set's; no layer applied
twice to a unit; a partial object over at least two units); the units against the
objects, per unit (an applied object of layer *s* lies within the spans of layer
*s* − 1, a partial one meets them, and the units at stage *s* or above are no more
than layer *s* can have reached, a partial object counting at most its span less
one); and the delivered stage, which must be the
one the objects give. Delivered and presented are separate measures: a set whose
layers were all refused meets its commitment with stage 0 presented. A delivered stage
may be stated with no commitment beside it, unlike §2.4.9's rule 3: the observer
states what arrived. Whether a commitment was met is derived, never stated.
- **The `delivery-gap` class** (§2.4.11): a relay's statement that it did not forward
the object at (`track_alias`, `group_id`, `object_id`) — `delivery-timeout`,
`discretion` or `not-received` — checked at step 6 against the object receipt at
those coordinates (`gap_subject_mismatch`).
- **Authorities** (§7.5): a `stage` claim needs `stage`, a `delivery-gap` claim
`delivery-gap`; neither is pinned-only, and `delivery` grants neither.
- **staged-delivery §10** is split: §10.1 attestation of layers, which now names
`delivered-verified` and `inclusion-verified` and the per-track chains, and §10.2
claims. `staged::claim::StageObserver` states a sink's `stage` claim from what it
observed: the layers that arrived, with the layer and span their own `refines`
states, those known only by their receipts, and every report the sink gave, the
latest deciding. Every event carries the sink's epoch for the base
(`StageSink::base_epoch`, which counts the times the sink has forgotten it): a later
epoch starts the observation over, keeping only the envelopes the sink still holds for
the base, and a claim never spans two epochs. Every case of `staged-conformance/` is replayed through a
sink and its observers, and each claim seals and states what the sink did. [http-delivery](extensions/http-delivery.md) §10.5 says what an
abandoned refinement is: `inclusion-verified`, and `dropped` or `late` in a `stage`
claim; [staged-measurement](extensions/staged-measurement.md) §10.2 says the same of
an object its simulator drops.
- **Corpora.** `jwp-conformance/` gains the `chain` procedure — vector
`v6_per_track_chain` (§5.9) and its neighbours: interleaved tracks, a track joined
mid-chain, a link across tracks, forks, the guard and the freshness bound — and the
`inclusion` procedure. `claim-conformance/` gains both classes (§5.10), every reason
they add, and a context for a gap's object receipt; `signer-conformance/` gains the
authorities they need. `jwp_verify.py`, `claim_verify.py` and `signer_verify.py`
report every new outcome; `differential.py` generates `stage` bodies around every
rule and gaps against receipts at and away from their coordinates, and
`chain_differential.py` signs chains around §3 step 3 and §6.1 for both verifiers.
Every vector already in the claim and key-discovery corpora is unchanged, and the
claim corpus pins the ones before the new classes by a digest computed before they
were added. In `jwp-conformance/`, `group_chain_break` alone changes, as the item on
forks and gaps says.
- No receipt or claim byte of existing material changes; one existing vector,
`group_chain_break`, is given a genuine chain break, as above.
### HTTP delivery (Draft)
- **The extension** [http-delivery](extensions/http-delivery.md) binds envelopes to
HTTP, with no envelope byte and no manifest member changed:
- the proposed media type `application/wai`, not registered;
- `codecs` elements `wai1.`/`wai2.` that list the exhaustive union of the
capabilities a variant may dispatch to or apply, with the floor every object of it
offers carried separately (`X-WAI-FLOOR`, a DASH `SupplementalProperty`);
- objects named by their receipt content hash (`<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.
- **Receipts, keys, grants and parameter sets** (http-delivery §8.1–§8.5, §10.5):
- a receipt file `rcpt/<h>.json` holds one group receipt (in the labelled payload) or
claim in JCS, at most 1 MiB, read as jwp-receipts reads one and refused unless its
link hash is its name (`receipt_name_mismatch`, `receipt_not_served`,
`receipt_too_large`); a legacy group, whose name does not cover its parent, is
refused (`receipt_legacy_group`);
- an index, at most 4 MiB with at most 256 delivery items and 256 claims
(`receipt_index_too_large`), is read item by item (`receipt_index_malformed`), each
receipt file fetched once per index, at most 1024 in all
(`receipt_fetch_budget_exhausted`) and 64 MiB read in all, each file read no further
than what remains of that (`receipt_fetch_bytes_exhausted`), each cap with its reason
stated: each delivery item's group goes through
jwp-receipts §3 against the client's chains, a missing parent fetched by its link up
to 64 of them, never more, and never skipped (`parent_not_held`, with where the walk
stopped), a joining client anchoring instead; its object receipt through steps 4
and 5 against what the client holds — the hash recomputed over the envelope, the cid
a head proof established (§8.3), or nothing, which is `inclusion-verified` and gives
no property reading (§10.5); each claim by its subject against the items that
passed, its parent fetched by its link, then §3.1 step 6 against what those items
deliver and the receipts it names (`figure_not_held`, `context_not_held` where the
client does not hold them; an energy claim naming an OER receipt, which this revision
does not carry over HTTP, is `context_not_held`), a declaration it names read as
§8.1 reads every file fetched by name before step 6 compares it, a recovery record
checked with the client's matchers whether or not it holds the object, and a soft
binding of another object attesting the ancestor only through §3.2's recovery,
under the held publisher key that signed it;
- a key log anchors only from the origin's own location over HTTPS, answered `200`
with no redirect (`key_log_not_from_origin`), from a retrieval the verifier made
itself, asking with `Cache-Control: no-cache`; a key log is read to 1 MiB and a longer
one refused, anchored or cached (`key_log_too_large`); a log through any cache adds revisions
and never moves the anchor, an older copy reported (`key_log_rolled_back`); a grants
file is a locator (`grants_file_malformed`); a key status is served for a quarter of
the head's `status_max_age_s` at most; a module grant is §7.9's form, and a JCP grant
is carried only by reference in the receipt that names it;
- a parameter set served by its digest is held only by its pin and §7.9
(`pin::verify_bytes_with`, `int_payload::resolve`), never by its name.
- `wai-rs`: `http_binding::receipts` and `http_binding::keys`;
`jwp_hash::EstablishedHash`, built only from bytes or by a fetch session whose head
proof verified (`FetchSession::established`); `ObjectReceipt::accept_hash` and
`verify_against_hash`, `WaiReceiptProperty::check_against_object_hash` and
`ReceiptChains::verify_admitted_object`, which the existing checks call;
`energy_figure::HeldObject`, an object receipt's figure for step 6; `wai http
receipts`, which fetches under a deadline and the caps above, follows no redirect,
and with `--key-origin` retrieves the key log from the origin itself over TLS
(the `cli_tls` feature: a TLS client validating against the operating system's
trust store; SNI and the certificate check name the origin's host, port 443 by
default, each address the host resolves to tried in turn), every step within one
deadline however slowly a peer answers; a build
without it refuses to anchor rather than fetch the log over plaintext, and the
test-only `--test-plaintext-connect` anchors with its own path, `test-plaintext`,
wherever the anchor or a confirmation is reported, as anchoring nothing a verifier
may rely on. A group or
object receipt whose `kind` is not a string is `wrong_kind`
(jwp-receipts §3 step 1), as the independent verifier already read it.
- The corpus serves receipts, indexes, a key log, a status, a grants file and a set;
`http_receipts.py` checks them for `http_verify.py` through the jwp-receipts, claim
and key-discovery verifiers, and the differential adds receipt files, indexes,
grants files and key logs.
- No envelope byte or receipt byte changes for existing material.
### Packaging: objects, the stage index, the floor, catalogs and the package receipt
- **Objects** ([packaging](extensions/packaging.md) §5): each one envelope, named by its
receipt content hash, with no manifest member naming a position, so a repeated piece
is one object. The packer's base encode calls `choose_q` per piece.
- **The stage index** `WSIX` (§6, `wai-rs` `stage_index`, feature `video_package`): per
component, every object of the package by content hash, its lengths, role, layer,
the tier and decoded-output digest of the prefix it completes, its SSE, and the class
plan; its digest and a Merkle stage root; a named reason for every rule a verifier
applies to an index and to the objects it lists.
- **The floor** (§7): a supplied AV1 floor, checked against the keyframe plan
(`wai-pack plan --out`) and the listing the policy pins, written as floor objects
that carry `derived_from` and as CMAF files (`init.mp4` with `av01`/`av1C`, one
fragment per piece).
- **SPEC §7.1, *Derived renditions***: `derived_from` on a manifest or a rendition
entry states that a payload presents another object and was made from it; an
OPTIONAL member under the MINOR rule that never breaks a parse (§8).
- **Catalogs** (§8): on http-delivery's Mode N, a classical master and floor playlist
that name nothing of WAI; per class, its master (with the one SESSION-DATA line
http-delivery allows), its WAI playlist with the codecs union and floor, and a WAI
media playlist adjacent to the floor's, with `#EXT-X-GAP` for a piece the class
takes as floor only and a `#WAI-LAYER` line for each refinement layer it lists (no
package of this revision carries one: the packer refuses refinement rungs, §11.4);
the classical and per-class DASH MPDs; the MoQ catalog; and a stage view per class.
- **The version-2 package receipt** (§9, `wai-rs` `package`): binds the policy digest
and each component's index by digest and stage root, with entry, object and byte
counts every verifier recomputes; RFC 8785 JSON both ways; a measurement claim beside
a metered figure. The version-1 receipt gains the same JSON form and keeps its
bytes. Both share the figure profile `wai:video-package-receipt-id`.
- **`wai-pack pack`, `verify` and `listing`**, and the corpus's golden package
`video-base-01`, its index, object and receipt mutations with their reasons, and
version-1 receipts as JSON. The registry lists packaging's wire identifiers.
- No envelope byte or receipt byte changes for existing material.
### Packaging: a declared policy, its digest, and pieces cut at scene changes
- **The extension** [packaging](extensions/packaging.md), first sections: a package is
made under one TOML policy, `wai.pack.policy/1` (§3), read in a fixed order with
every refusal named: value kinds a canonical JSON cannot carry exactly (floats,
dates, negatives, integers past `2^53 − 1`), then the schema, every default written
in, then the constraints across members (ladders and their refinement chains,
classes, rules, deadlines against layer lags). The effective policy's digest is
BLAKE3 over its RFC 8785 form, so two spellings of one policy have one digest.
- **Pieces** (§4): `wai.pack.scenecut/1` cuts a video component at scene changes by
integer histogram distance, with a bounded lookahead whose return and revisit
clauses keep a short flash from cutting; a decision reads no frame past the
lookahead, so live and on-demand packing cut alike. Pieces are bounded by `min` and
`max`, aligned across components to the lead, and classified static, synthetic,
high-motion or natural. Y4M and WAV sources convert by stated integer rules.
- **`wai-pack`**, the reference packager, as far as a plan (`wai-pack policy`,
`wai-pack plan`), with float arithmetic denied for the whole crate, and the corpus
[`pack-conformance/`](pack-conformance/) with a second implementation in Python.
- **What a packager may emit** (§3.6): only capabilities SPEC §5 registers as payloads
with a pinned format, in a role each can play, and under `royalty_free_only` only
royalty-free ones; every refusal names the capability (`capability-unknown`,
`capability-not-payload`, `capability-candidate`, `capability-role`,
`license-unclassified`, `license-refused`).
- **One integer video encoder** (§5.5): `int_video_enc` codes RGB8 frames as the
`WIV1` keyframe-0 payload SPEC §5 pins to `wai.video.int_motion`, with integer block
motion search (whole- or half-pel) over the decoder's own prediction, residuals
quantized in a closed loop, and one residual table for the clip; it states the SSE
of its reconstruction. `int_payload::encode_wiv1_zero_motion` is its zero-motion
case and writes the bytes it wrote before. The corpus holds its payloads for every
piece of every fixture, decoded independently to the same frames.
- **`wai::jcs`**: the canonical JSON form moves out of `claim_receipt` into a module
of its own, compiled under every feature set, unchanged in behaviour.
- **Choosing the step** (§5.6), specified and built ahead of the stage that writes
objects: the largest of `q_candidates` (read as a set) whose SSE per sample, in
thousandths, is at most `max_sse_px_milli`, else the smallest
(`wai_pack::encode::choose_q`), with goldens recomputed independently.
- **Reading what a packager is given**: a policy is read from its bytes (UTF-8, no
byte-order mark, nesting at most 32 deep, `policy-toml` otherwise), ids are unique
across every kind, a cut window holds at least 3 scores, a WAV block alignment is
compared without wrapping, and no declared size sizes a buffer; the segmenter's state
is bounded by its lookahead and window. The encoder codes in two passes and states
what it holds (`int_video_enc::working_bytes`), and `rans::RansReverseEncoder` codes
values last-first into the same bytes.
### Staged delivery in the browser, a base writer, a trace and a bench
- **A base is bounded before it is decoded, and its chain from the start**
([staged-delivery](extensions/staged-delivery.md) §4.5, §4.2, §14). A base whose units'
output, with 32 bytes for each stage its kept record holds, passes the chain budget is
not added: its envelope is reported `chain-over-limit`, no chain is kept, its kept
record is untouched, and its layers stay held. Where a base's capability declares a
cost before decoding (the integer payloads SPEC §5 pins), a sink applies its cost
limit to that declared cost first, and SPEC §4 treats a base past it as one it does
not decode, moving on to the fallback; a capability that declares no cost
(`wai.neural.int_synth`, `wai.neural.int_mlicpp`) decodes under its format's caps. The corpus gains `base_over_budget`;
`staged_verify.py` implements the rule independently.
- **The sink in the browser.** `wai-web` binds `wai::staged::StageSink` for JS
(`src/stage.rs`). `StageBase.decode` decodes a base envelope by SPEC §4 through the
integer path, with its pin verified against the parameter set the page holds in
memory and under the page's cost limit, which is a required argument (`null` for the
format's caps only; it bounds a base whose capability declares a cost);
`StageBase.fromStage0` takes a base the page decoded elsewhere, which is **claimed**:
its capability must be the manifest's or its fallback and one SPEC §4 would take (an
inert envelope is refused), its tier is never above what the capability gives an
unpinned base, and every report, unit and stage hash of its chain says `claimed`. A base's facts cannot
be raised from JS (no `viaFallback` setter; `attested` only rises), and `addBase`
takes the base. `StageSink` adds bases, offers layers, expires holds, removes, attests
and forgets bases, and reads back every unit's stage, producer, frozen flag, tier,
shape, output, stage hash (with its `claimed` mark) and squared error against a
reference. `stageLayerInfo` reads a layer's `refines`, `WIR1` header, declared cost and
byte split (framing, manifest, payload), and a base's declared decode cost, before
either is offered. Reports and units come back in the corpus's own form. Media reach
the page as `WAI1` bytes only.
- **The browser equals the native sink.** `wai-web/demo/verify_staged_wasm.mjs`
replays every case of `staged-conformance/` through the wasm32 build: each integer
base is decoded in wasm32 and must give its `stage0.bin`, its tier and its fallback
flag, and every report, final unit, stage hash and clip hash must equal what the
native sink wrote. `wai-web-wasm.yml` runs it and watches the corpus; the crate's
native tests replay the corpus through the binding's pure-Rust half as well.
- **One base path.** `staged::decode_base` runs SPEC §4 for a base through
`Wai::dispatch_verified`: its capability, then its fallback, each only where it is
dispatchable, so a refinement-only or companion-only capability makes the envelope
inert (`missing_capability`) and never reaches its fallback. It takes the sink's cost
limit (checked before any symbol is decoded or output allocated, `cost_over_limit`)
and its key log (SPEC §3.1 check 5 over a pin and over an unpinned set; none skips
it). `decode_base_via` asks for one of the two capabilities and refuses any other
(`not-offered`). The refusals come in a stated order: `not-wai1`, then `encrypted`
(SPEC §6.4 before §4), then `not-offered`, then §4's; a parameter set is read only
for a capability that reads one. Its own tokens are staged-delivery's kebab-case sink
vocabulary; SPEC and payload codes pass through in snake_case. A pin counts toward
the base's tier only where it resolved. A
one-file set pinned without a form offers every held file to the pin, the member the
capability reads first. The CLI and the browser share this path.
- **A keyframe-0 base writer.** `int_payload::encode_wiv1_zero_motion` codes RGB8
frames as a `WIV1` keyframe-0 payload (zero motion, one block per frame, a closed
group of pictures): the reference encoder the corpus generator used, now in the
library. The corpus regenerates byte for byte through it. It refuses a clip past the
format's caps before allocating (`too_large`). `wai stage base` writes a
`wai.video.int_motion` base with it from raw frames, with its dimensions multiplied
checked, and decodes it back before writing.
- **A trace to check against.** `wai stage apply` is now a sink replay: envelopes are
taken in order, routed by content (one with `refines`, or a refinement-only
capability, is a layer; any other is a base, decoded by SPEC §4), and a base given
after its layers releases the layers held for it. `--json` prints each step's reports
in the corpus's form (bytes that are not a `WAI1` this sink reads, and a base it
cannot decode, are the step's error by their code, and the replay goes on; a base
read with `--stage0` is claimed, and its step, units, chain's reports and final entry
say so), and every stage a unit reached in the step, in the
order the sink applied the layers (`StageSink::set_trace`), so a batch that takes a
unit up several stages lists each; every stage hash a delivery in that order reaches
is in it. A test replays the corpus through it and requires the cases' reports and
final units.
- **`wai_stage_bench`.** Measures one staged set natively: per stage, the envelopes'
bytes (framing, manifest and payload, and cumulative), PSNR or SNR per unit and over
the clip from the exact integer squared error against the source at the units' final
shape (a unit of another shape has no figure at that stage), the time each stage took
(median and quartiles over at least 20 runs), the direct comparator (one layer per
base at the stage's step, straight from stage 0, or why it does not apply) and every
unit's stage hash. Energy is reported as unmetered.
### Staged delivery: refinement layers, base admission, and WAI2 refinement components
- **The extension** [staged-delivery](extensions/staged-delivery.md): a stage set is a
base and layers, each layer its own `WAI1` envelope naming, by content hash, the
base and the producer each unit it covers is expected to have (`refines`: `base`,
`prev` as one producer or runs, `layer`, `span`). A sink holds per unit a stage, a
producer and a tier, and applies a layer per unit by one algorithm with a closed
list of reasons (§4.1, §4.2): every envelope-scoped step (well-formed `refines`, a
refinement capability with no fallback, the base present, not through a fallback,
admitted, attested, a well-formed payload within the sink's cost limit and its chain
budget) before the unit steps (conflict, readiness, geometry, deadline). Layers a unit is not ready for are held
to a declared bound and released when its stage advances; layers released together
form a batch, so two conflicting layers released together are both refused and the
unit frozen, whatever their order. A layer that differs from one that already passed
for the same unit freezes it at its stage. A unit's tier is the weakest of its base
and layers, `entropy-consistency` does not pass through a layer, and a stage hash is
stated only at `decode-equivalence`. Deferral, live deadlines (a sink never delays a
unit for a layer, and counts the units presented below the stage their layers would
have reached on time), attestation (a receipt verified without the layer's bytes
never authorizes an application) and byte counting (envelope bytes; receipts,
framing and parameter sets separately; never JSON or base64) are stated.
- **Resource bounds** (§4.5, §14, A.5): a sink declares a chain budget per base, over
its units' output, 32 bytes per stage, and the payload and declared working bytes of
every layer it retains with one unit's output and 32 bytes for each unit it covers
and has not been applied to; a layer that would pass it is refused
(`chain-over-limit`) before any stream is decoded, and a layer no unit is held for is
dropped. The declared working bytes count the stream index. A sink's work per layer
is linear in its payload and declared symbols up to a logarithmic factor in its
units, and §14 states how. A sink that drops a base's chain discards the layers held
for it (`base-absent`) and keeps each unit's record that is not empty (its
producers, a pending producer, its frozen flag), within a declared record budget
whose oldest bases are forgotten first; the base decoded again advances only along
that record, and a frozen unit replays only its own record, in order.
Decoding a base again keeps its chain, and a receipt verified for the base, before or
after it was decoded, makes it attested from then on, across dropped chains; a layer held for its base and offered
again keeps the strongest receipt evidence, and over an attested base counts as
arriving when its receipt was verified; a set split across bases shares one live
clock (`first_unit`).
- **The residual payload `WIR1`** (Appendix A): one rANS stream per unit;
`clamp(pred + r·q)` with the identity, up2 (the half-pel arithmetic, odd sizes
included) and rate (linear interpolation, `rdiv`'s halves away from zero, channels
interleaved) predictors. It follows the integer payloads' rules: a parse in a
stated order with the closed list of their codes, caps, a declared cost a sink reads
before decoding, refusal at the first symbol a stream runs out on, canonical stream
ends; and it reads no parameter set.
- **Refinement capabilities** (SPEC §5, *Refinement capabilities*):
`wai.image.int_refine`, `wai.video.int_refine`, `wai.audio.int_refine` and
`wai.splat.int_refine`, each refinement-only, royalty-free, `StandardDefined` and
exact given the base it refines. SPEC §8 adds the refinement-only role; SPEC §4 never
dispatches one, and a sink that does not implement the extension finds it
unsupported. The registry gains the `refinement` kind and role, and the export is
regenerated. *Envelopes this makes non-conforming* (they still parse): a `WAI1`
carrying a refinement capability without `refines`, with a fallback, or as a
fallback; `refines` on any other capability; a refinement capability as a `WAI2`
rendition or companion; a companion bound to one.
- **Base admission is one table** (SPEC §5, *Base admission*;
`codecs::BASE_ADMISSION`): the bases of each refinement and of the LCEVC enhancement
layer, whose bases are unchanged. `enhancement_bases`, `companion_applies_to` and
`admits` read it.
- **WAI2 refinement components** (SPEC §7.1), on the component identity (`id`,
`sha256`, `prior`; no other digest is defined): an entry with
`role: "refinement"` carries `refines` naming entries by `id`, follows every
selectable entry, is never selected, and is checked by `check_component_roles` in a
stated order. *Exploding a component* writes its `WAI1` envelope, from
`envelope_manifest` verbatim or from the `WAI2` manifest's members as written, with
ids replaced by the content hashes of the named entries' exploded envelopes, so a
component and the layer carried alone are the same object; `WaiMulti::pack` packs
`WAI1` envelopes with their manifests verbatim, and they explode back to the same
bytes; it refuses an envelope it could not give back (bytes around a manifest's JSON
value or after a payload, or a manifest that inside a `WAI2` passes the depth limit)
and checks its output before returning it. Each entry explodes on its own, so one
that cannot leaves the others intact. A refinement's `envelope_manifest` names no
fallback. A malformed `refines` or a mismatched `envelope_manifest` does not make a
manifest unreadable.
- **SPEC §2**: member names are unique within each object of a manifest, at any depth,
and an integer member is written as an integer (`-0`, `1.0` and `1e0` are not); both
readers refuse a manifest that breaks either. `span: null` is `span` absent.
- **Reference implementation**: `staged::{StageChain, StageSink, wir1, predict,
wir1_arith, tier, schedule}` (feature `staged`); `container::{Refines, Prev}`;
`WaiMulti::{explode, explode_each, explode_all, pack, check_envelope_manifests,
refinements_of}`; `container::check_unique_members`; `wai stage
refine|apply|inspect|pack|explode` (`refine --out-dims` gives a layer's shape);
`StageSink::{remove_base, forget_base, mark_attested}`; `wai wrap` refuses a refinement
capability, and `wai inspect` shows `refines`. `RansEncoder` writes the bypass
escape, and `int_entropy::hist_to_cdf` builds a table from a histogram in integer
arithmetic. In `ternary::rans_encode_trits`, the encode call sat inside
`debug_assert!`, so a release build coded no symbol; it now runs in every build.
- **Conformance**: the corpus [staged-conformance](staged-conformance/) (image, video,
audio and splat sets; cases for every reason of §4.2 and every `WIR1` refusal code,
in the order the steps check them, and for §3's rules, holds and their bound,
batches, frozen units, the chain budget, a base decoded again, receipt evidence and
live deadlines, one clock across bases included; `WAI2` cases for each refinement
rule of SPEC §7.1, for exploding entry by entry and for what a packer refuses; the
live schedule), written and verified by `wai_staged_vectors` and verified
independently by `staged_verify.py`, which shares no code with `wai-rs`; the pure-
Python BLAKE3 moves to `claim-conformance/_blake3.py`, shared by both Python
verifiers. Cross-architecture goldens pin every stage of an image, a video and an
audio set; Kani proves, over bounded windows, the arithmetic step, the up2 bounds,
`rdiv` and the rate indices. `rdiv` and the rate indices are computed in checked
64-bit arithmetic.
### Staged-delivery measurement: counted bytes, integer quality, the output tree
- **The extension** [staged-measurement](extensions/staged-measurement.md): how a
figure about staged delivery is measured and published. The counted bytes of an
object are its envelope to the byte (`12 + man_len + P` for `WAI1`,
`8 + man_len + 2 + 8n + Σ P` for `WAI2`), and an object is countable only where WAI
checks the format of every payload it carries: the pinned integer formats and `WIR1`,
by the parse a sink runs, for the capability and for a `WAI1` fallback with a pinned
format. Any other capability, a classical codestream or an unregistered string among
them, is not countable (`no-format-check`), and neither is a `WAI2` component whose
`sha256` is not its payload's, nor an encrypted payload; no estimate stands in for a
figure that is not countable. A figure carries the framing label `registered`. Quality is integers per
presentation and per prefix, against the source and against the final prefix: SSE
over RGB and over an integer BT.601 luma, a Q32 structural similarity over clipped
8×8 windows at stride 4 with an edge-aligned last window on each axis, so every pixel
is in a window (bounded in 128-bit integers), PCM SSE and reference energy,
and tensor SSE and largest difference, each pooled over units; decibels are display
only. First presentable, layering overhead as two rate–quality points (never a
ratio), delivery simulation, decode time and energy (only a clean attribution verdict is a figure; a rail
that is not live is unmetered), deduplication, dated records, what CI asserts, and a
media-class matrix are stated. The output tree (Appendix C) commits to every
presentation, one SHA-256 leaf per (unit, layers), under RFC 6962's tree hash, by its
root and leaf count together, with audit paths checked as RFC 9162 checks them under
the committed count (`output_tree::OutputCommitment`).
- **Measured titles** (Appendix D): a staged set over a staged-conformance base, read
where it is, with only what the stage model does not carry beside it (unit duration,
capture times, the source, single-layer references, the fixture tool's own
reconstruction digests), and a content identity over the measured files.
- **Reference implementation.** `wai-rs` feature `stage_measure` (pure integer, no
ONNX, wasm32-clean; CI builds it for wasm32): `measure_framing`, `output_tree`,
`stage_measure::{title, decode, run}`, which decodes each base through the sink's own
base decode, `staged::decode_base_pinned` (which also gives the pin it verified),
under the cost limit, and applies the layers through `staged::StageSink`
under the chain budget, handing each stage's output over as it is reached. The tools'
stated limits are `staged::TOOL_DEFAULT_LIMIT` and `staged::TOOL_DEFAULT_CHAIN_BUDGET`
(staged-measurement Appendix D states each figure and why); `wai stage` (`apply`,
`refine`, `base`), `wai_stage_measure eval` and `wai_stage_bench` decode every base
under them, hold every layer and chain to them, and take
`--limit OUT,SYMBOLS,WORKING,MACS`. A title's `limits` lower a limit and never raise
one: each figure is the lower of the title's and the tool's.
The corpus's `refusals/` hold titles a tool must refuse, with the code: a base past its
chain budget, the two declared-clip bound vectors (refused before anything is
allocated for them; a counting-allocator test shows it), the 1 GiB one again under a
title that states limits of 2^40, two inert envelopes, and a layer past its title's
cost limit. `tests/vectors/staged_bounds/` gains a base whose up2 layer declares
67,092,480 output bytes, which `wai stage apply` and `wai_stage_bench` refuse with no
flag. Always on:
`quality` and `content_dedup` (the package receipt's dedup figures now go through
it; a map that gives one content hash two lengths no longer verifies); native only:
`bench_env`, the machine record, which `wai_decode_bench` now prints from. The
`wai_stage_measure` tool evaluates a title (`eval`), verifies the corpus (`verify`)
and writes it (`gen-fixtures`, `generate-goldens`).
- **The corpus** [`staged-measure-conformance/`](staged-measure-conformance/): a
picture, a clip, an audio clip and a splat attribute tensor, each with its figures in
`goldens.json`; `wai_stage_measure verify` and an independent Python verifier,
`measure_verify.py`, both reproduce every figure, and CI runs both.
- **The delivery simulator** (§10, Appendix B; `stage_measure::sim`,
`wai_stage_measure sim`): a discrete-event model in integer microseconds of a sender
putting a title's objects on one link (`strict-priority` or `fifo`; `datagram` or
`subgroup` carriage with delivery timeouts that drop or reset; a layer cap; a VOD
window or live capture) and a sink decoding and presenting them (workers, `base-first`
or `fifo` decode, playout deadlines). Its figures are labelled simulated. Bases are
sent, and decoded under `base-first`, in the order they became available. A
refinement's stall of its base is then bounded by one chunk (strict priority) or a
timeout and one chunk (`fifo`), plus one refinement decode (`base-first`): §10.4
proves it, for every plan with no base timeout, a bounded link schedule and
`base-first` decode. It is also checked on generated plans drawn over that whole
range and on the corpus's plans, and for each unbounded part a counter-plan's stall
passes the bound of the same plan with that part restored. The corpus `sim/` and an independent
`sim_verify.py` reproduce every result.
- **Wire.** No envelope, payload, receipt, claim, pin or corpus file of an earlier
revision changes bytes.
### Delivery telemetry: CMCD version 2 and CMSD for WAI objects
- **The extension** [delivery-telemetry](extensions/delivery-telemetry.md): an unsigned,
advisory observation about WAI delivery (no joule figure, no acquisition class, no
quality figure; content and receipt hashes only as pointers), read strictly with
exact members; its CMCD version 2 report, in Request mode for requests and Event mode
otherwise (`e=rr` for responses, play states, custom events for switches, dispatch
outcomes, layer-cap changes and dropped frames), with header shards; custom keys
under `science.transaction.wai-`, each carrying a String; switch reasons; CMSD-Static
and CMSD-Dynamic for a WAI object; session metrics from one session's log (video
start time, exit before start, start failure, stalls, engagement, units, counted
bytes); and privacy rules: a content id is a content hash or a short token, and an
emitter puts no address, query, token or personal data in it; an object URL holding a
`?`, `#`, `@` or `\` is refused rather than stripped, so no query, signed-URL token or
user information enters an observation however a parser splits the URL; and a
Request-mode report goes only to the host serving the object. Cohort assignment and capable share are not in this revision.
- **Reference implementation.** `wai-rs` feature `telemetry` (no dependency;
wasm32-clean): `telemetry::{sf, observation, cmcd, cmsd, qoe}`, with the RFC 9651
subset the reports use; the `wai_telemetry` tool.
- **The corpus** [`telemetry-conformance/`](telemetry-conformance/): observations of
every event kind with their reports, refused observations, CMSD descriptions and
session logs. `wai_telemetry verify` requires every value; a differential encodes
every report with an independent open-source CMCD and CMSD library at locked versions
and requires the same bytes, reads them back, and validates them; CI runs both.
- **Other text.** [moq-streaming-format](extensions/moq-streaming-format.md) §5 requires
that telemetry **MUST NOT** restate the per-object property's figure, class or evidence;
[energy-exposure-mapping](research/energy-exposure-mapping.md) gains a dated
addendum.
- **Wire.** No envelope, payload, receipt, claim or corpus file of an earlier revision
changes bytes.
### The integer codecs' payloads pinned in place: `WIV1`, `WIH1`, `WIA1`, `WIS1`
- **Pinned in place** (SPEC §5, *Payloads of the WAI-native integer codecs*, and the
new extension [integer-payloads](extensions/integer-payloads.md)): the payload of
`wai.video.int_motion` is `WIV1` with keyframe 0; of `wai.video.int_hyper`, `WIV1`
with keyframe 1, a nested `WIH1`; of `wai.neural.int_hyper`, `WIH1`; of
`wai.audio.int_codec`, `WIA1`, which carries the sample rate; of
`wai.splat.int_codec`, `WIS1`, which carries each attribute's presentation range.
These strings' rows named a decoder and no byte layout; three of them
(`wai.video.int_motion`, `wai.audio.int_codec`, `wai.splat.int_codec`) also named
the tool that wrote a JSON bitstream, which SPEC §5 called the bytes the codec
normally emits. So the pin changes what these strings carry. SPEC §8 gains
*Completing a registration*, which permits it for a WAI-native string whose row
fixed no byte layout and lists the envelopes it makes non-conforming; from this
revision their formats are fixed as SPEC §8 states. No new string is registered.
`codecs::PINNED_INT_PAYLOADS` is the one table of strings and magics.
- **The JSON is a converter input, never a payload.** The encode and export tools
still write JSON; `int_payload::{wiv1,wih1,wia1,wis1}_from_json` convert it and
state what it left implicit (an absent `mv_precision` is 1; the first frame carries
no motion vectors; a short motion-vector list is padded with `(0, 0)`; the audio
sample rate comes from the export's `audio.json`; a splat range must be exactly a
binary32 value; the JSON `afrac` must be the model's; the channel count must match
the medians, tables and offsets). *Envelopes this makes
non-conforming:* a `WAI1` or `WAI2` that carries anything but the pinned format
under these strings, a tool's JSON output among them. It still parses; a sink
refuses it at decode (a JSON form with `bad_magic`) and does not try the fallback.
Every emit path refuses to write it: `Wai::to_emit_bytes` and
`WaiMulti::to_emit_bytes` (so the C packers `wai_envelope_pack` and
`wai_envelope_pack_multi`, `WAI_ERR_INVALID_ARG`), `wai-web`'s `pack` and
`packMulti`, and `wai wrap`. They run `container::check_int_payload_framing`, which
parses the payload with the wire parser a sink runs and the capability's keyframe
kind, so it checks every parse rule of the extension's §2–§6 and leaves to the
decode only what needs a parameter set or the streams (§7); `wai inspect` reports
what it finds. A refused emit is the new `EmitError::IntPayload`. An encrypted
envelope's payload is ciphertext (SPEC §6), so the emit paths skip it and a sink
checks the plaintext after decrypting (SPEC §6.4); `wai inspect` says so. No
committed envelope or conformance corpus carried such a payload. The reference
dispatchers (`Wai::dispatch_verified`, `WaiMulti::pick`, and so the neural sink,
its C ABI and `wai-web`'s `dispatchCapability`) implement SPEC §6.4 step 2: they
open no scheme, so an envelope whose manifest declares an encryption other than
`"none"` is inert at them (`unsupported_encryption`; `container::Inert::Encrypted`,
`container::Skip::Encrypted`, `neural::DecodeError::Encrypted`), and a payload sent
in the clear under a claim of encryption is not decoded as plaintext. A `WAI2`
manifest's `security` member (now stated in SPEC §7.1) must be a security object to
be written (`EmitError::Security`, `container::manifest_v2_security`); one that is
not is read as encrypted.
- **One integer decode path.** `int_payload::decode_int` decodes the strings above
and `wai.neural.int_synth` and `wai.neural.int_mlicpp` from parameter files held in
memory, with no ONNX runtime and no filesystem, natively and for `wasm32`, through a
dispatchable capability only. A refusal is one code from a closed list
([integer-payloads](extensions/integer-payloads.md) §8). `int_payload::resolve`
verifies a pin over sets held in memory and runs SPEC §3.1's check 5 for a pinned
decode and over the set an unpinned decode reads. The neural sink delegates every
integer capability to it: integer video, PCM and splat attributes come back as
`neural::Decoded::Int`, never converted; `ModelRegistry::builtin` advertises
`wai.video.int_motion`, which reads no set; the C ABI refuses an `Int` output. A
capability this build has no integer decoder for (`unsupported_capability`) moves
dispatch on to the fallback, as SPEC §4 treats a capability a sink cannot decode.
Check 5 covers every identity of the bytes a decode reads (SPEC §3.1): after a pin
verifies, it runs again over the listing of the members read and a one-file set's
file digest (`int_payload::read_identities`), so a set revoked under one identity
is not reached through a pin naming it by another; and a listing beside an
unpinned set counts only when it matches the files read. Outside the integer path,
at a sink that holds a key log, an unpinned decode reads what it can read once —
the registered file of a one-file set, or every file below the directory its
decoder reads from (`pin::read_dir_set`) — and check 5 runs over every digest a pin
over it could name: the file's, or the directory's derived listing
(`pin::listing_of_dir`) and each file's, and each listing beside them that matches.
The decode reads only those bytes, never the path again; where the key log holds
the capability's sets to jwp-receipts §7.9, only the part §7.9 authorised (the
whole, the files a granted listing names, or one granted file). So a revoked set
is refused with no listing beside it, a granted listing authorises the files it
names and nothing else, a file changed after the check is not read, and a file the
decode needs outside the set is `set_incomplete` (SPEC §4, *Module authorisation*).
`pin::PinAuthority` gains `requires_grant`, with a default.
*Breaking:* `neural::Decoded` gains the variant `Int` and is now `#[non_exhaustive]`,
so a `match` on it outside this crate needs a wildcard arm; `neural::ParamSource`
gains `UnpinnedRead` (an unpinned integer set read into memory once) and
`Checked` (what an unpinned decode at a sink with a key log reads, read once) and is
`#[non_exhaustive]` too; `int_payload::IntParams` is opaque, built by
`int_payload::resolve` (or `IntParams::none()`).
- **Checked decoders.** `int_video`, `int_audio` and `int_splat` decode a typed
stream that the binary and JSON forms both read into. Every size is capped at
`2^26` elements (a `WIV1` clip at `2^30` bytes and `2^20` frames, its motion-vector
field at `2^26` elements) the same on every target, every network's shapes are
planned before anything is allocated, every latent and layer is checked for
exactness, and a stream that runs out is refused at the first symbol it cannot
give, so a build with overflow checks and one without accept the same bytes. A
frame, a clip or a latent grows only as its symbols are decoded, to its declared
size and no further, so a short stream stops early; `int_video` holds frames as
RGB8. A stream that carries its symbols is decoded in full, and a few kilobytes can
code a clip at the cap, so the header bounds the work: a sink reads the declared
cost before decoding (`int_payload::declared_cost`, `container::declared_size`, the
C ABI's `wai_int_payload_declared`, `wai-web`'s `intPayloadCost`) and applies its own
size policy (integer-payloads §7, *Memory and work*). The cost counts the networks'
multiply-accumulates layer by layer, exactly (`DeclaredCost::macs`, a `u128`;
`macs` on `intPayloadCost`, a `BigInt`). The reference sink applies one:
`neural::ModelRegistry::with_cost_limit` refuses a payload whose declared cost
exceeds the limit before any symbol is decoded, with the new code `cost_over_limit`
(`int_payload::check_cost`, `neural::DecodeError::OverCostLimit`), and dispatch moves
on as for a capability the sink cannot decode (§8). `neural::declared_cost` reads the
cost over the set a registry resolved, pinned or not. The C ABI's
`wai_int_payload_declared` takes the set's path (or null for the header alone) and
gives the working bytes and the multiply-accumulates too (`wai_int_declared_t`
gains `working_bytes`, `macs_lo`, `macs_hi` and `cost_known`); its neural decode
entry points no longer decode integer video, PCM or splat attributes, which they
have no output kind for, and refuse them before decoding. A `WIA1`'s latent may be no
longer than its `t_out` needs (`geometry_mismatch`). The JSON reader
(`int_video::VideoJson`) caps every size before it pads a motion-vector list, and
reads a field that does not fit the target's `usize` as absent rather than
truncating it. In the browser, `decodeWiv1` keeps the decoder's frames
(`DecodedVideo.frame(i)`) instead of copying the clip into one buffer.
- **One refusal order.** The extension states the order of a decode's checks
exactly (§7): the set's members, the set against the payload, every network
planned with its caps, the shapes, then each stream symbol by symbol, a run-out
before the value it would give. Networks are planned layer by layer in 64-bit
arithmetic (`int_hyper_synth::plan`, the audio synthesis's plan), a size past 64
bits being past the cap, so a 32-bit and a 64-bit sink refuse a malformed network
with the same code; a planned network's parameter lengths are checked before any
symbol is decoded, and a convolution's padded side is capped. A set's entropy
tables obey §2's length rule (3 to 4097 entries). The parser reads a table of one
per channel table by table. A `WIV1` keyframe's planned size is checked against the
clip's `h × w` before any of the keyframe's symbols is decoded, so
`int_payload::declared_cost` and the decode refuse the same bytes with the same
code; the Rust verifier checks that for every fixture. Fixtures pin each case.
- **One header for a one-frame clip.** A `WIV1` of one frame codes no motion
vector, so its `mv_precision` and `block` must be 1 (`field_range` otherwise); the
converters write them so. The extension's §2 says a payload's hash identifies the
payload, not its output (unused table entries, say, code the same clip in other
bytes), and that one output is identified by its decoded digest (a clip's
`clip_hash`).
- **Canonical streams.** After its last symbol, each rANS stream of these payloads
must have been read to its last word and end in the state `2^31`, where an encoder
starts (`rans::RansDecoder::is_canonical_end`); otherwise the payload is refused
with the new code `stream_noncanonical`. An unread word or another starting state
coded the same symbols in other bytes. Every committed stream, the JSON forms'
included, ends canonically. `int_hyper_synth` gains
`decode_conditional_checked`; `int_synth` refuses a side of `2^64` or more instead of
shifting by it.
- **Parameter sets.** SPEC §5 names the members the integer decoders read:
`g_s.whm`, `h_s.whm` and `entropy.json` for `wai.neural.int_hyper` and
`wai.video.int_hyper`. `wai.audio.int_codec` (`model.wam`) and
`wai.splat.int_codec` (`g_s.whm`) are registered as one file, which widens their
registrations (SPEC §8). `wai registry export` writes nothing new: no exported field
records a set's shape. A `WHM1` or `WAM1` network holds nothing after its last layer
(`IntModel::from_bytes` and `AudioModel::from_bytes` refuse trailing bytes; every
committed network has none), so one network has one file and one digest.
- **Receipts.** A `wai.video.int_motion` receipt identifies its `WIV1` payload
(`VideoReceipt::seal_decode_wiv1`, `verify_reconstruction_wiv1`), and
`wai_video_meter` seals over it. The entry points that read the JSON form are
deprecated; on a conforming JSON bitstream their output is unchanged, so every
receipt sealed over a committed one, and every pinned digest of one, still
verifies. They now read the JSON through `int_video::VideoJson` and the checked
decoder, so they refuse JSON the old decoder took: a stream that runs out (it
decoded on from the exhausted state) or does not end canonically, a table that
breaks the table rules, `q` outside 1..=65535, an `mv_precision` other than 1 or 2,
other than one motion-vector list per frame, vectors on the first frame or more
vectors than blocks, a vector outside `i16`, a zero size, and a size past the caps. jwp-receipts §2.1's object content hash moves to its own
module and feature (`jwp_hash`), which `receipt_claims` enables;
`claim_receipt::object_content_hash` re-exports it, and no corpus changed.
- **JSON readers that refuse.** `IntModel::try_from_json` and
`CondBitstream::try_from_value` read the conditional codec's debug JSON without
panicking; `wai-web`'s `decodeIntHyper` and `decodeIntHyperBin` read it through
them, `CondEntropy::try_from_value` and `decode_conditional_checked`, so an empty
model or bitstream, or sizes past the caps, are refused. `int_video::VideoJson`
refuses a frame whose motion vectors are not a list.
- **Browser.** `wai-web` decodes the four formats (`decodeWiv1`, `decodeWih1`,
`decodeWia1`, `decodeWis1`); the entry points that read the JSON forms are for
debugging and conversion.
- **Conformance.** The payloads sit beside their JSON sources in
`wai-rs/tests/vectors/`, written by `wai_int_payload_vectors`, with refusal fixtures
and an index in `int-payload-conformance/`; `int_payload_verify.py` checks them
independently in Python's standard library and decodes the `WIV1` keyframe-0
payloads in full; `byte-exact-conformance` parses and decodes the `WIV1` and `WIH1`
payloads on every leg of its matrix. CI runs the new tests in both build profiles,
the generator in the corpus loop, the verifier, a `cargo check` of each integer
feature alone, and a `wasm32` check.
### The registry as one table, candidate capabilities, the codec glossary under test, and the public text retrofit
- **Candidate capabilities** (SPEC §5): a registered string whose payload format no
revision has pinned — `wai.tensor.nnc` and `wai.splat.sog` — MUST NOT be emitted, and a
sink treats it as unsupported. This is a MINOR rule: it enforces the candidate
registrations as they were made (§8). Envelopes it makes non-conforming: a `WAI1` naming
a candidate as its `capability` or `fallback`, and a `WAI2` with a component of a
candidate; they still parse. `codecs::is_candidate`, `Wai::check_candidates` and
`WaiMulti::check_candidates` report them. `Wai::to_emit_bytes` and
`WaiMulti::to_emit_bytes` check, then serialise: the C packers `wai_envelope_pack` and
`wai_envelope_pack_multi` emit through them, the browser writers `pack` and `packMulti`
refuse the same envelopes (`container::check_candidates_v1`,
`check_component_candidates`), and `wai wrap` refuses a candidate before any other rule,
in every place including a `WAI2` companion. A candidate's licence class is provisional:
`wai.splat.sog` is classified patent-pool, conservatively (it was royalty-free, never
established). `wai.splat.sog` no longer claims an MPEG attribution: no MPEG page or
output document read names the format it was registered for ([research
registry](research/media-codec-registry.md), *MPEG's Gaussian-splat and point-cloud
work*).
- **One registry table** (`wai-rs/src/codecs/registry.rs`, no feature): every registered
string with its **kind** (`payload`, `companion`, `derivation`, `record`), its
**status** (`registered` or `candidate`; `RegistrationStatus`), its **media** class,
the SPEC §5 heading that registers it, the edition its registration names and the date
it was registered; the named identifiers that are not capabilities (`wai.det.fixed64`,
`wai.world.sim.*`); and the receipt labels no revision registers
(`wai.quantum.{mitigate,decode,compile,rearrange,qbom}`). SPEC §5 states the kinds:
records and derivations name no payload format. Every payload and companion
capability carries a licence class (records do not).
- **Dispatchable** (SPEC §4, normative): steps 3 and 5 take only a dispatchable
capability, and a capability is dispatchable unless this revision registers it as a
candidate, a companion-only capability, a record or a derivation. A string this
revision does not register stays dispatchable — a later MINOR revision may register
it (§8) — and is decoded only where the sink advertises it. `codecs::is_dispatchable`
implements it and is the one predicate every dispatch and selection function of the
reference implementation uses (`Wai::dispatch_verified`, formerly
`dispatch_capability`; `WaiMulti::pick`; `WaiMulti::primary`;
`RenditionMeta::is_selectable`; the CLI's `inspect`; and, below, the neural sink, the
C ABI and wai-web); §7.1's selection sentence says the same. A later dispatch or
selection function uses it too.
- **`wai registry export`** writes `registry/capabilities.json`, its JSON Schema
(`registry/capabilities.schema.json`), the generated table of
[`REGISTRY.md`](REGISTRY.md) and **vocab v2** (`vocab/v2.{ttl,jsonld,shapes.ttl}`,
namespace `https://wai.transaction.science/vocab/v2#`, one IRI per registered string,
enumeration individuals named `<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).