open-standards/wai/extensions/model-governance.md
Transaction Science 8638f156f6
Some checks failed
joule-moq-agent AI participants / joule-moq-agent · does this change touch it (push) Successful in 24s
joule-moq-http live HTTP egress / joule-moq-http · does this change touch it (push) Successful in 20s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 26s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 39s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 41s
WAI operation catalogue / does this change touch it (push) Successful in 37s
wai-quantum studio and desktop / does this change touch it (push) Successful in 37s
wai-quantum for JavaScript / does this change touch it (push) Successful in 40s
wai-sink-android native sink library / does this change touch it (push) Successful in 31s
wai-fleet service / does this change touch it (push) Successful in 1m17s
wai-ledger service / does this change touch it (push) Successful in 1m49s
wai-ops-gpu integer operators on GPUs / does this change touch it (push) Successful in 1m42s
wai-audit collector and player integration / does this change touch it (push) Successful in 1m55s
wai-app and Create / wai-app · does this change touch it (push) Successful in 1m58s
wai-pack / wai-pack · does this change touch it (push) Successful in 1m43s
WAI Studio / does this change touch it (push) Successful in 1m19s
wai-sink-swift native sink package / does this change touch it (push) Successful in 1m20s
wai-ops-gpu integer operators on GPUs / Vulkan GPUs (framework-128gb) (push) Failing after 4s
wai-ops-gpu integer operators on GPUs / Vulkan GPUs (jetson-hub) (push) Failing after 1s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 13s
WAI examples / WAI examples · does this change touch them (push) Successful in 20s
wai-quantum studio and desktop / desktop app · linux (push) Failing after 19s
wai-quantum for JavaScript / javascript · linux (push) Failing after 7s
wai-fleet service / service · linux (push) Failing after 5s
wai-ledger service / service · linux (push) Failing after 7s
wai-audit collector and player integration / collector · linux (push) Failing after 20s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 1m7s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Failing after 10s
wai-quantum studio and desktop / studio · browser (push) Successful in 4m56s
wai-pack / wai-pack · linux (push) Successful in 5m50s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 14m21s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (push) Successful in 20m56s
joule-moq-agent AI participants / joule-moq-agent, every target (push) Successful in 3m0s
WAI Studio / Studio in WebKit · linux (push) Successful in 26m25s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 2m20s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 3m21s
joule-moq-http live HTTP egress / joule-moq-http, every target (push) Successful in 4m43s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Failing after 11m38s
wai-pack / wai-pack · the floor through a media prober (push) Successful in 1m26s
wai-pack / wai-pack · dependency licences (push) Successful in 45s
wai-pack / wai-pack · linux-arm64 (push) Successful in 5m46s
joule-moq-room room service / joule-moq-room, every target (push) Failing after 9m57s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 8m38s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 12m11s
WAI examples / WAI examples · portable · linux (push) Successful in 26m40s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 1m5s
wai-pack / wai-pack · independent verifier (Python) (push) Successful in 27m21s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 4m19s
wai-ledger service / service · macos (push) Successful in 3m50s
wai-fleet service / service · macos (push) Successful in 4m37s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Failing after 21m52s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 6m3s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 9m26s
wai-pack / wai-pack · macos (push) Successful in 9m19s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 23m37s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (push) Failing after 21m11s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 15m22s
WAI examples / WAI examples · android-app · linux-arm64 (push) Successful in 19m53s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 27m55s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 14m45s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Successful in 27m48s
Model policies: run reports count their sinks' refusals, and a roll-up may state the reports refused and its decodes by tier (D82)
A run report may carry refusals: the decodes its sink refused by its checks, by code, before the
report's end (model-governance 6.1). Every sink counts them in Governed::admit, which the native
decoder, its C interface and wrappers, and a page's paths call. A roll-up that states refusals
names the reports its operator refused with their reasons (7.3); the bundle holds them, a verifier
refuses each again by its signature or step 5's checks for the reason stated (8.2 step 6), and
every class line counts them by reason and sums its reports' refusals, every set line its decodes
by tier. rollup_stating signs such a roll-up; the earlier form reads and verifies as before.
Corpora: models adds an accepted roll-up and fifteen refusals of it; model-sink's reports state
the refusals each counts, checked through the C interface and recomputed by the second
implementation.

Answers waiTransport wai-additions 53.4 and 53.5.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-12 07:55:23 -05:00

760 lines
50 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 Extension: Model Governance
> Status: Draft. Registers the record `wai.sink.models`: which parameter sets a fleet's sinks
> may run, and what they ran. An operator signs a **model policy**: its sinks in **classes**,
> each class's devices named by their keys, by a **roster** (the root of a Merkle tree over
> their keys, each device proving its own place), or vouched for by an attestation key, and for each
> class the parameter sets it may run, each named by the SHA-256 a pin names it by
> ([SPEC](../SPEC.md) §3.1), for a capability and a role (a codec's or DRED's set, an exact
> upscale's prior, an enhancement model, a generator), with the determinism tier it must run
> at; the policy holds for a span, a successor takes its place, and the operator's key log
> ([jwp-receipts](jwp-receipts.md) §7) revokes a set by its digest. A sink signs a **run
> report** for a period, built from what it already states: the report of
> [neural-dispatch](neural-dispatch.md) §5 for each neural decode it ran (the unit, the
> backend and the tier), with the set the decode read, and the presentation statements of
> [exact-upscale](exact-upscale.md) §6.2 its sessions signed (the pictures it presented that
> were transmitted, derived exactly, edited or generated, each sink-made one with the digest of
> the prior or model that made it). A verifier holding the policy, the key log and the report
> refuses a report in which a set outside the policy ran, a revoked set ran after its
> revocation, the policy was no longer in force, or a path in floating point claims a decode
> that needs `decode-equivalence`. The operator signs a **roll-up** per period: for each class,
> the reports, the devices that reported and those that did not, and each set that ran with
> its decodes by unit, its frames and its pixels; a verifier recomputes it from the reports.
> A policy whose classes name rosters is the same size whatever its fleet's, and a verifier that
> checks many bundles reads each policy once, by its link hash, so a report's check reads nothing
> of the fleet but its own device's proof (§8.4). A policy of no class ends what a fleet may run
> (§4.3). A report also counts the decodes its sink refused, and a roll-up may state the reports
> its operator refused, each held in its bundle and refused again, and its decodes by tier (§7.3).
> No new transport, and nothing a sink signs today changes. The reference implementation is the
> `model_policy` module of `wai-rs` (feature `model_policy`); the corpus is
> [`live-conformance/models/`](../live-conformance/models/), and its second implementation is
> `live-conformance/model_policy_verify.py`.
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
>
> Texts read for this revision: the WAI texts this one cites; RFC 3339; RFC 8032; RFC 8785;
> FIPS 180-4; RFC 9162 §2.1 (the Merkle tree hash, inclusion paths and their verification).
## 1. Scope
In scope: the records of this extension, their encodings, signatures and identities (§3); the
model policy, its classes and the sets each may run, its span and its successor (§4); the
roster and a device's proof of its place in it (§4.5); the device attestation that places a
device in a class (§5); the run report and the runs it states (§6); the roll-up (§7); the bundle
a verifier is given, its verification, and a verifier of many bundles (§8).
Out of scope: how a sink obtains a set, which [prior-carriage](prior-carriage.md) states; how a
sink is told to load or unload one; whether a set's bytes are the ones a pin names, which SPEC
§3.1 checks at the sink before any decode; who may sign a decoder module, which jwp-receipts
§7.9 governs; and the presentation of a run to a viewer, which [synthesis](synthesis.md) states.
A policy says which sets a fleet's sinks may run. It does not stop a sink from running another:
a sink that runs one says so in its report, and a report that leaves a run out is §12's
concern.
Relationship to the core spec: `wai.sink.models` is a **record** ([SPEC](../SPEC.md) §5): a
signed record a WAI tool emits and verifies. It names no payload format and is not
dispatchable.
Relationship to [fleet](fleet.md): a fleet enrols the nodes that carry media to sinks; this
extension enrols the sinks, by their own keys, in classes of a policy. The two share no record.
## 2. Terms
- **Operator**: the party that runs a fleet of sinks. Its **operator key**, an Ed25519 key
(RFC 8032), signs its policies and roll-ups. Its key log, where it keeps one, is published
under an origin of jwp-receipts §7.1.
- **Device**: a sink of the fleet, named by its Ed25519 key, the **device key**, which signs
its run reports.
- **Attestation key**: an Ed25519 key that vouches for devices (§5): the operator's own
provisioning key, or a key of the party that made the devices.
- **Class**: a named set of devices that run under the same rules.
- **Set**: a parameter set (SPEC §3.1), named by the **digest** a pin names it by: its SHA-256
in the pin's form. A prior of exact-upscale is pinned with no form, by the SHA-256 of its one
file (exact-upscale §3.5). A sealed set (prior-carriage §18.2) has a second identity, the
SHA-256 of its plaintext, `inner`.
- **Role**: what a set is run as. `decode`: the set a capability decodes against, a codec's
networks and tables, DRED's weights ([opus-dred](opus-dred.md) §4.1) or a generator a
capability decodes through. `upscale`: the prior of `wai-upscale:sr2` or `wai-upscale:sr4`
(exact-upscale §3), which makes a `derived` picture. `enhance`: a model that edits a
transmitted picture by a step no verifier recomputes, which makes an `asserted` picture.
`generate`: a model that makes a `synthesized` picture. A tone map reads no parameter set
([exact-tonemap](exact-tonemap.md)), and no policy names one.
- **Tier**: a token of SPEC §3.1, ordered `none` < `entropy-consistency` <
`decode-equivalence`.
- **Run**: one use of a set by a device: a decode (§6.2) or a sink-made picture (§6.3).
- **Time**: a **log date-time** is jwp-receipts §7.2's (`YYYY-MM-DDTHH:MM:SSZ`), and two of them
compare as strings; a **date-time** is jwp-receipts §2.4.3's. A date-time is compared with
another, or with a log date-time, by the instant it names, as jwp-receipts §7.4 compares two:
brought to UTC by its offset, then by its second as written, then by its fraction. A
**span** or a **period** runs from its first instant (included) to its last (excluded).
## 3. Records
The records of this extension are JSON objects under the encodings exact-upscale §2 uses: byte
strings in lowercase hexadecimal, unsigned integers of at most 2^53 − 1 in plain decimal, no
other number, no boolean, terms and date-times as jwp-receipts §2.4.3 defines them, no member
named twice, at most 32 levels of nesting, exactly the members a section lists at every level,
and a `kind` naming the record. One that does not read so is `record_malformed`.
Every record carries `signer_pubkey` (32 bytes), `issued_at` (a date-time, when its signer
signed it) and `sig` (64 bytes):
```
sig = Ed25519(signer_key, "wai:sink-models\x01" || JCS(record without "sig"))
link_hash = BLAKE3("wai:sink-models-link\x01" || JCS(record))
```
JCS is RFC 8785, over the values these records carry as jwp-receipts §2.4.3 reduces it. The
signature is verified strictly ([SPEC](../SPEC.md) §7 *Signatures*): a key that fails step 1 or 2
is `bad_key`, and a signature that fails a later step `bad_signature`. A record is named by its
link hash.
A **name** is 1 to 64 bytes of `a`–`z`, `0`–`9`, `-`, `_` and `.`. An **origin** is an origin of
jwp-receipts §7.1: `https://`, a host and, where one other than 443 is written, `:` and a port.
## 4. The model policy
### 4.1 Layout
A **model policy**, `kind` `"wai-model-policy"`, is signed by the operator key:
| member | type | meaning |
|---|---|---|
| `kind` | string | `"wai-model-policy"` |
| `seq` | integer | 0 for the operator's first policy; one more than the policy it supersedes |
| `supersedes` | 32 B hex or `null` | the link hash of the policy it replaces; `null` exactly when `seq` is 0 |
| `not_before`, `not_after` | log date-time | its span |
| `key_log` | string or `null` | the origin whose key log revokes sets for this fleet (§4.4), or `null` for none |
| `classes` | array | the classes (below) |
| `issued_at` | date-time | when the operator signed it |
| `signer_pubkey` | 32 B hex | the operator key |
| `signer_id` | string | 1 to 2048 bytes: the operator key's `signer_id` (jwp-receipts §7.1) |
| `sig` | 64 B hex | §3 |
A **class**:
| member | type | meaning |
|---|---|---|
| `name` | string | its name (§3) |
| `devices` | array of 32 B hex | the device keys the policy enrols in it |
| `attesters` | array of 32 B hex | the attestation keys whose devices are in it (§5) |
| `sets` | array | the sets its devices may run, each an entry (below) |
| `roster` | object | only in a class that names its devices by a roster (§4.5): exactly `root` (32 B hex), the root of the roster's tree, and `count` (integer), how many keys it holds |
A class has exactly the first four members, or those and `roster`. A class that lists its devices'
keys in `devices` grows with its fleet; one that names a roster is the same size for any fleet.
An **entry**:
| member | type | meaning |
|---|---|---|
| `sha256` | 32 B hex | the set's digest (§2) |
| `capability` | term | for `decode`, the capability the set is decoded through; for every other role, `wai.media.upscale`, the record whose presentation statement states the run (exact-upscale §6.2) |
| `role` | string | `decode`, `upscale`, `enhance` or `generate` |
| `tier` | string | the least tier a run of the set holds (§6.2, §6.3) |
### 4.2 Rules
A policy reads when it has the members and types of §4.1 at every level, its times are log
date-times and each entry's `capability` is a term; otherwise it is `record_malformed`. A policy
that reads and whose signature verifies is refused `policy_invalid` when it breaks any of these:
- `supersedes` is `null` exactly when `seq` is 0;
- `not_before` is before `not_after`;
- `key_log` is `null` or an origin (§3);
- `classes` holds 0 to 256 classes, in strictly ascending order of `name`, each `name` a name
(§3); a policy of no class permits nothing (§4.3);
- in each class: `devices` holds at most 4096 keys and `attesters` at most 16, each list in
strictly ascending order; a class with a `roster` lists no `devices`, and its roster's `count`
is 1 to 2^32; at least one of `devices` and `attesters` is not empty, or the class has a
`roster`; `sets` holds 1 to 1024 entries,
in strictly ascending order of (`sha256`, `capability`, `role`), compared as byte strings;
- in each entry: `role` is one of the four of §2 and `tier` a tier token; for `upscale`,
`enhance` and `generate`, `capability` is `wai.media.upscale`; and for `enhance` and
`generate`, `tier` is `none`, since a picture no verifier recomputes holds no tier (§6.3).
A device may be in several classes, and a set in several entries.
### 4.3 The successor
A policy is replaced by its **successor**: a policy of the same operator key whose `supersedes`
is its link hash, whose `seq` is one more than its own, and whose `not_before` is not before its
own. A policy is **in force** from its `not_before` until the earlier of its `not_after` and its
successor's `not_before`, where a verifier holds its successor. So a successor that begins
before its predecessor's span ends takes its place from then, and a gap between a policy's
`not_after` and its successor's `not_before` is a time when neither is in force.
A successor states its classes whole: a set that it does not list is not permitted from its
`not_before`, whatever its predecessor listed.
**A policy of no class.** A policy whose `classes` is empty enrols no device and permits no set. As
a successor it is how an operator says that its fleet may run nothing from an instant: from its
`not_before` no device is in a class (§6.4 step 3, `device_not_in_class`), so no run report under
it is accepted, and a sink that holds it refuses every decode ([native-sink](native-sink.md) §14).
It ends what its predecessor permitted as any successor does, and its own successor, where the
operator signs one, may permit sets again. No other record ends a policy: the key log revokes sets,
not policies (§4.4).
**The chain a report is held to.** A report is signed by a device that held the chain as it stood
when it signed. A successor ends its predecessor's time in force for a report only where it was
issued no later than the report: its `issued_at` is not after the report's `issued_at` (§6.4 step
2). A successor the operator issues later, whose `not_before` reaches back over a period a device
has already reported, binds the reports signed after it and leaves earlier ones as their devices
signed them, so a report one verifier accepted is not refused by a later bundle that holds a later
successor. A report is signed once its period has ended (§6.1), so a device cannot place its report
before a successor and keep running under the predecessor past the successor's start.
### 4.4 Revocation
A policy that names a `key_log` takes its revocations from that origin's key log (jwp-receipts
§7.2): a set is **revoked** from the `issued_at` of the first revision of the log whose
`revoked_modules` lists its digest, and stays revoked, since every later revision lists it (rule
6 there). This is the list SPEC §3.1 check 5 reads at the sink, under the same digest, so a set
the operator revokes is refused at every sink of the fleet that holds the log, and a report of a
run after the revocation is refused here (§6.2, §6.3). A verifier reads the log as jwp-receipts
§7.3 checks a log before anchoring it, for the policy's origin, as a log its relying party pins.
A policy that names no key log revokes nothing: its successor drops a set instead (§4.3).
### 4.5 The roster
A **roster** is the list of device keys a class enrols by commitment: 1 to 2^32 keys, in strictly
ascending order as byte strings, so each key is in it once and `count` is how many devices it
enrols. The class names it by the root of a Merkle tree over those keys and by its count:
```
leaf(key) = SHA-256("wai:sink-models-roster-leaf\x01" || key)
node(l, r) = SHA-256("wai:sink-models-roster-node\x01" || l || r)
root = MTH(leaf(key_0), ..., leaf(key_{n-1}))
```
with `MTH` RFC 9162 §2.1.1's Merkle tree hash over those leaf hashes, its interior node `node`:
the root of one leaf is that leaf, and of *n* > 1 leaves `node(MTH(D[0:k]), MTH(D[k:n]))`, *k* the
largest power of two below *n*. The leaf and node domains differ, so no node passes for a leaf.
A device's **membership proof** is an object of exactly `index`, an integer, its key's place in the
roster from 0, and `path`, an array of at most 32 hashes (32 B hex): RFC 9162 §2.1.3.1's inclusion
path for that leaf, from the leaf's level upward. A proof that does not read so is
`record_malformed`. It shows a key to be in a class's roster when RFC 9162 §2.1.3.2's verification,
given `index`, the class's `count` as the tree size, the path, `leaf(key)` and the class's `root`,
succeeds: `index` is below `count`, every node of the path is used and none is asked for past the
last, and the walk ends at the root. The size is the class's, which the operator signed, never the
proof's: one path can reach one root under two sizes, and a size the device chose would let it
choose its place. Under the class's size each node stands for one subtree, so a key proved at one
index is the roster's key at that index, and the proof of one key proves no other.
A verifier checks a report's proof against the class of the policy the report names (§6.4), so a
proof made under one policy's roster proves nothing under a successor whose roster differs: a
device dropped from a successor's roster has no proof that reaches its root, and one kept in it
has the proof of the successor's tree. A proof costs one leaf and at most 32 nodes to check: 12
for a roster of 4000 keys and 17 for 100 000, one more each time the roster doubles, and nothing
else of the fleet.
The roster itself, a JSON object of exactly `kind`, `"wai-model-roster"`, and `keys`, the keys in
the roster's order (32 B hex), is published by the operator as it publishes its policies; this
text does not say where. It is signed by no one: the policy signs its root and count. A verifier
given it computes its root and count (§8.2 step 1), and holds the class to §4.2 and this section's
rules over its keys; a roll-up rests on it (§8.2 step 6). A verifier given only a report and its
proof learns that the device is in the class, and nothing of the other keys.
## 5. The device attestation
A **device attestation**, `kind` `"wai-model-device"`, is signed by an attestation key and
places a device in each class that names the key in `attesters`:
| member | type | meaning |
|---|---|---|
| `kind` | string | `"wai-model-device"` |
| `device` | 32 B hex | the device key |
| `not_before`, `not_after` | log date-time | the span it holds |
| `issued_at` | date-time | when its signer signed it |
| `signer_pubkey` | 32 B hex | the attestation key |
| `sig` | 64 B hex | §3 |
One whose `not_before` is not before its `not_after`, or whose `device` is its `signer_pubkey`,
is `record_malformed`.
## 6. The run report
### 6.1 Layout
A **run report**, `kind` `"wai-model-run"`, is signed by a device key for a period:
| member | type | meaning |
|---|---|---|
| `kind` | string | `"wai-model-run"` |
| `policy` | 32 B hex | the link hash of the policy the device ran under |
| `class` | string | the name (§3) of the class it ran as |
| `attestation` | 32 B hex or `null` | the link hash of the device attestation that places it in the class; `null` when the class enrols its key |
| `from`, `to` | log date-time | the period, `from` before `to` |
| `decodes` | array | at most 65 536 decode entries (§6.2), in order of `at`, each `at` not before the one before it |
| `presentations` | array of 32 B hex | at most 4096 link hashes of presentation statements its sessions signed (§6.3), in strictly ascending order |
| `issued_at` | date-time | when it signed it: not before `to`, since a report states a period that has ended |
| `signer_pubkey` | 32 B hex | the device key |
| `sig` | 64 B hex | §3 |
| `membership` | object | only in a report whose device proves its place in its class's roster: its membership proof (§4.5) |
| `refusals` | array | only in a report of a sink that refused a decode in the period: the decodes it refused, by code (below) |
A report has exactly the members of this table but `membership` and `refusals`, or those and either
or both of them; it carries `membership` only with `attestation` `null`. A report whose `issued_at` is before its `to`, by the
instant each names (§2), or that breaks another rule of this section, is `record_malformed`. A
report with no decode and no presentation states that the device ran no set in the period.
**Refusals.** A sink that holds its policy refuses a decode before it runs ([native-sink](native-sink.md)
§14), and keeps no refused decode (§6.2), so its runs alone would not show that its device keeps
asking for a set it may not run. A report's `refusals` counts those refusals: 1 to 8 entries, each
exactly `code`, one of the codes of the sink's checks (`device_not_in_class`, `model_not_in_policy`,
`model_revoked`, `model_runs_full`, `model_tier_below_policy`, `policy_expired`,
`policy_not_yet_valid` and `record_overlap`), and `count`, an integer from 1, in strictly ascending
order of `code` as byte strings: the decodes the sink refused with that code at an instant before
`to` that no earlier report of the device counted. A decode refused for its payload, or for its pin,
is the decoder's refusal, not the policy's, and is not counted. A report whose sink refused nothing
has no `refusals`. Like its decodes, the count is the device's statement; nothing in a bundle checks
it, and a roll-up sums it (§7.2).
### 6.2 Decodes
A **decode entry**:
| member | type | meaning |
|---|---|---|
| `at` | log date-time | when the decode ran, on the device's clock |
| `set` | object | `{"sha256", "inner"}`: the digest of the set the decode read, as its pin named it, or as a pin over the bytes it read would name them where it was unpinned (SPEC §3.1, *Pin by default*, item 4); and, for a sealed set, its `inner` digest, `null` otherwise |
| `dispatch` | object | the report the sink gave for the decode (neural-dispatch §5), as it gave it |
The `dispatch` member reads as neural-dispatch §5 states its report: exactly the members `kind`
(`"wai-neural-dispatch/1"`), `capability` (a term), `needed` (a tier token), `ran` and `refused`
(an array of at most 16 paths). A path has exactly `unit` (`npu`, `gpu` or `cpu`), `backend`
(`model-runtime`, `webnn`, `onnx-runtime`, `wai-ops` or `wgsl`) and `arithmetic`; the path in
`ran` adds `tier` (a tier token) and `placement` (`null`, or exactly `npu`, `gpu`, `cpu` and
`unknown`, each an integer); a refused path adds `reason`, a code of neural-dispatch §7. A path's
`arithmetic` is its backend's (neural-dispatch §1: `float` for `model-runtime`, `webnn` and
`onnx-runtime`, `integer` for `wai-ops` and `wgsl`), and a path in `integer` names no `npu`
(neural-dispatch §3). In a run report, `ran` is a path: a decode that no path ran ran no set,
and is not a run. A decode entry that breaks a rule of this paragraph is `record_malformed`.
A decode's **capability** is its dispatch's `capability`, its **unit** the `unit` of `ran`, and
its **tier** the `tier` of `ran`. A verifier checks each decode entry of a report, in order, and
the first check that fails is the reason:
1. `at` is in the report's period (`outside_period`).
2. Its path's tier is the one neural-dispatch §3 lets it hold: a path in `float` runs no decode
whose `needed` is `decode-equivalence`, and holds no tier of `decode-equivalence`
(`dispatch_tier_refused`). A neural processor's run claimed at `decode-equivalence` is
refused here, whatever the device says of it.
3. The device's class has an entry whose `sha256` is the set's `sha256`, whose `capability` is
the decode's and whose `role` is `decode` (`model_not_in_policy`).
4. The decode's tier is not below that entry's `tier` (`model_tier_below_policy`).
5. Where the policy names a key log: neither the set's `sha256` nor its `inner` is revoked from an
instant at or before `at` (`model_revoked`). A sealed set has two identities, and SPEC §3.1
check 5 runs over both; so does this check.
### 6.3 Pictures a sink made
Each link hash in `presentations` names a presentation statement (exact-upscale §6.2), signed
under a key of one of the device's sessions. The run report binds the statement to the device;
the statement is not changed. A statement's **instant** is its `issued_at`: a sink signs a span's
statement once the span has ended, so every picture it states was made at or before that
instant.
Each picture a statement lists (each entry of its `frames`) whose `made` is not `transmitted` is
a run of the set its `made_by` names, in the **role** its category gives: `derived` runs an `upscale` set, `asserted` an
`enhance` set and `synthesized` a `generate` set, each for the capability `wai.media.upscale`;
its **tier** is `decode-equivalence` for `derived`, whose derivation is integer throughout
(exact-upscale §3), and `none` for the other two. A verifier checks each statement a report
names, in order, and then each of its pictures, in order; the first check that fails is the
reason:
1. The verifier holds the statement (`presentation_unknown`).
2. No earlier report, and no earlier position of this one, names it (`record_duplicate`): one
statement is counted once.
3. Its instant is in the report's period (`outside_period`).
4. For each picture whose `made` is not `transmitted`: its `made_by` is not `null`, and the
device's class has an entry whose `sha256` is its `made_by`, whose `capability` is
`wai.media.upscale` and whose `role` is the picture's (`model_not_in_policy`). An `asserted`
picture whose model the statement does not name (exact-upscale §6.2 allows `null`) was made
by a model no policy can permit.
5. Its tier is not below that entry's `tier` (`model_tier_below_policy`).
6. Where the policy names a key log: its `made_by` is not revoked from an instant at or before
the statement's instant (`model_revoked`).
A picture's **pixels** are its count in its own category: `pixels.derived` for `derived`,
`pixels.asserted` for `asserted`, `pixels.synthesized` for `synthesized`, as the statement
counts them (exact-upscale §6.3).
### 6.4 The device and its policy
Before its runs, a verifier checks a report's device and policy, in this order:
1. The policy it names is one the verifier holds (`policy_unknown`).
2. The policy is in force over the whole period, as the report's device held the chain (§4.3):
its `not_before` is not after `from` (`policy_not_yet_valid`), and `to` is not after the end of
the time it is in force for the report (`policy_expired`): its `not_after`, or its successor's
`not_before` where that is earlier and the successor's `issued_at` is not after the report's
`issued_at`. A report of a period that runs past a policy's `not_after`, or past the start of a
successor issued before the report, is refused here; one that runs past the start of a successor
issued after it is not.
3. The device is in the class: the policy has a class of that `name`, and either `attestation`
is `null`, the report has no `membership` and the class's `devices` holds the report's
`signer_pubkey`; or the report has a `membership`, the class has a `roster` and the proof shows
the report's `signer_pubkey` to be in it (§4.5); or `attestation` names
a device attestation the verifier holds whose `signer_pubkey` is one of the class's
`attesters`, whose `device` is the report's `signer_pubkey` and whose span holds the period:
its `not_before` not after `from` and `to` not after its `not_after` (`device_not_in_class`).
A proof for another device's key, one made under another policy's roster, and one whose index
or path the class's tree does not have are each refused here, as is every report under a
policy of no class (§4.3), which has no class of any name.
Of two reports by one device whose periods share an instant, the later is refused
(`record_overlap`): a device states each instant of its runs once.
## 7. The roll-up
### 7.1 Layout
A **roll-up**, `kind` `"wai-model-rollup"`, is signed by the operator key for a period:
| member | type | meaning |
|---|---|---|
| `kind` | string | `"wai-model-rollup"` |
| `policy` | 32 B hex | the link hash of the policy every report it rests on names |
| `from`, `to` | log date-time | the period, `from` before `to` |
| `reports` | array of 32 B hex | the link hashes of the reports it rests on, at most 65 536, in strictly ascending order |
| `classes` | array | one class line for each class of the policy, in the policy's order |
| `issued_at` | date-time | when the operator signed it |
| `signer_pubkey` | 32 B hex | the operator key |
| `signer_id` | string | as §4.1 |
| `sig` | 64 B hex | §3 |
| `refused` | array | only in a roll-up that **states refusals** (§7.3): the reports the operator refused, at most 65 536, each exactly `report` (32 B hex, its link hash) and `reason` (a string, the reason §8.2 refuses it for), in strictly ascending order of `report` |
A roll-up has exactly the members of this table but `refused`, or all of them.
A **class line** has exactly `class` (its name), `reports`, `devices` and `silent` (integers) and
`sets` (an array of set lines); a **set line** has exactly `sha256`, `capability`, `role`,
`decodes`, `frames`, `pixels` (integers) and `units`, which has exactly `npu`, `gpu` and `cpu`
(integers). In a roll-up that states refusals, every class line also has `refused`, an array of
entries of exactly `reason` (a string) and `reports` (an integer), and `refusals`, an array of
entries of exactly `code` (a string) and `count` (an integer); and every set line also has `tiers`,
which has exactly `none`, `entropy-consistency` and `decode-equivalence` (integers). A roll-up that
does not read so is `record_malformed`.
### 7.2 The figures
A class line's figures are computed from the reports the roll-up rests on whose `class` is its
name:
| figure | what it is |
|---|---|
| `reports` | those reports |
| `devices` | the distinct device keys that signed them |
| `silent` | the keys the policy enrols in the class, its `devices` or the keys of its roster (§4.5), that signed none of them |
| `sets` | one set line for each (`sha256`, `capability`, `role`) of a run in those reports, in strictly ascending order of the three |
and a set line's, over those reports' runs of that set, capability and role:
| figure | what it is |
|---|---|
| `decodes` | its decode entries (§6.2) |
| `units` | of those, how many ran on each unit |
| `frames` | its sink-made pictures (§6.3) |
| `pixels` | the sum of their pixels (§6.3) |
and, in a roll-up that states refusals, these besides:
| figure | what it is |
|---|---|
| a set line's `tiers` | of its decode entries, how many ran at each tier: the `tier` of their `ran` (§6.2) |
| a class line's `refused` | one entry for each reason of the roll-up's `refused` entries whose report names the class, its `reports` how many of them, in strictly ascending order of `reason` |
| a class line's `refusals` | one entry for each code of its reports' `refusals` (§6.1), its `count` their sum, in strictly ascending order of `code` |
A device attested into a class is never `silent`: a policy names the attestation keys of such a
class, not its devices, so only a device that reports is known to be in it.
### 7.3 Refused reports
An operator that verifies the reports its devices send refuses some, and the figures of §7.2 rest
on the reports it accepted. A roll-up that **states refusals** says how many it refused and why,
and rests that on the refused reports themselves: its `refused` names each by its link hash with
the reason, its bundle holds each in `refused` (§8.1), and a verifier refuses each again for the
reason stated (§8.2 step 6), so the counts are recomputed as every other figure is. A refused
report is counted in the class line of the class it names, which is a class of the roll-up's
policy, and its period lies in the roll-up's. A text that does not read as a run report names no
class, and a roll-up counts it nowhere. A refused report adds to no other figure: its device is not
among a class's `devices`, and is `silent` if it sent no accepted report.
A device's report states the decodes its sink refused (§6.1); a class line's `refusals` sums them
over the accepted reports, so an operator learns that its devices keep asking for a set the policy
does not permit, or for one it revoked, from the reports it already holds.
## 8. The bundle and its verification
### 8.1 The bundle
A **bundle** is JSON text (RFC 8259) in which no object names a member twice, no string holds a
surrogate that is not one of a pair, and nothing is nested more than 32 levels deep, the bundle
being the first; whether a number or a boolean it holds is one a record may carry is the
record's question (§3). It is one object with exactly these members: `kind`, the string
`"wai-model-bundle"`; `policies`, an array of 1 to 64 policies, each one the successor of the one
before it; `key_log`, a key log of jwp-receipts §7.2 (the document, with its `kind` and
`revisions`) or `null`; `attestations`, an array of device attestations; `presentations`, an
array of presentation statements; `reports`, an array of run reports; and `rollup`, a roll-up or
`null`; each record an object. It may also have `rosters`, an array of rosters (§4.5), and, where
its `rollup` is not `null`, `refused`, an array of at most 65 536 objects, the reports its roll-up
states refused (§7.3); and no other member. A bundle that is not such an object is refused
`record_malformed`, at `["bundle"]`. A file that holds one is named with the suffix
`.wai-models.json`.
**A bundle holds the window its reports need.** A bundle need hold only the records its reports
and its roll-up name: the policies they name, in the chain's order, each followed by its successor
where its holder has one (without it a verifier takes a policy to be in force until its
`not_after`, §4.3), and those between, since each policy but the first is the successor of the one
before it; the key log where one of them names one; the attestations and statements its reports
name; and, with a roll-up, the rosters of its policy's classes. A report that names a policy its
bundle does not hold is refused `policy_unknown`, whatever else its verifier has read. So the
bundle of one report is the same size however many policies, devices and reports the operator
holds, and the 64 policies a bundle may hold bound one bundle, not a chain: an operator whose
chain is longer gives each bundle the window its reports need.
### 8.2 Steps
A verifier given a bundle runs these steps and stops at the first refusal, which it reports with
the reason and where:
1. **Policies**, in order, at `["policy", i]`: each reads and its signature verifies (§3:
`record_malformed`, `bad_key`, `bad_signature`); it keeps §4.2's rules (`policy_invalid`); and
each but the first is the successor (§4.3) of the one before it (`policy_chain_break`). Then
**rosters**, in order, at `["roster", j]`: each reads as §4.5 has it (`record_malformed`: its
members, its `kind`, 1 to 2^32 keys); its root and count are those of a class of a policy the
bundle holds (`roster_mismatch`); and its keys are in strictly ascending order, as §4.5 requires
of the roster that class's policy signed (`policy_invalid`).
2. **The key log**: for each policy, in order, that names a `key_log`, the bundle holds a key log
(`key_log_absent`, at `["policy", i]`), and the log passes jwp-receipts §7.3's checks before
anchoring for that origin, at `["key_log"]`, with the reasons of jwp-receipts §7.7
(`key_log_malformed`, `origin_mismatch`, `key_log_signature_invalid`, `key_log_chain_break`,
`key_log_root_not_committed`, `key_log_rewrites_history`).
3. **Device attestations**, in order, at `["attestation", i]`: each reads and its signature
verifies (§3, §5); and none has the link hash of one before it (`record_duplicate`).
4. **Presentation statements**, in order, at `["presentation", i]`: each reads and its signature
verifies as exact-upscale §2 and §6.2 have it (`record_malformed`, `bad_key`,
`bad_signature`); and none has the link hash of one before it (`record_duplicate`).
5. **Run reports**, in order, at `["report", i]`: each reads and its signature verifies (§3,
§6.1); its device and policy (§6.4); its decodes, at `["report", i, "decode", j]` (§6.2); the
statements it names, at `["report", i, "presentation", j]`, and their pictures, at `["report",
i, "presentation", j, "frame", k]` (§6.3); and it shares no instant with an earlier report of
its device (`record_overlap`).
6. **The roll-up**, where the bundle holds one, at `["rollup"]`:
1. it reads and its signature verifies (§3, §7.1);
2. its `signer_pubkey` is the policies' (`wrong_operator`);
3. its `policy` is one the verifier holds (`policy_unknown`);
4. its `reports` are exactly the link hashes of the bundle's reports, and each of them names
its `policy` (`rollup_mismatch`): a roll-up rests on exactly the reports its bundle holds;
5. each report's period lies in its period: the report's `from` not before its `from`, and
the report's `to` not after its `to` (`outside_period`);
6. where it states refusals (§7.3): each report of the bundle's `refused`, in order, at
`["refused", k]`, reads as a run report (§6.1, `record_malformed`) and is none of the bundle's
reports and no earlier one of `refused` (`record_duplicate`); its `refused` names exactly the
link hashes of those reports (`rollup_mismatch`, at `["rollup"]`); and each of them, in order,
at `["refused", k]`, names a class of its policy (`rollup_mismatch`), lies in its period
(`outside_period`), and is refused, by its signature (§3) or by step 5's checks of it made
after the bundle's reports, for the reason the roll-up states (`rollup_mismatch`). A roll-up
that states no refusals beside a bundle that holds refused reports is `rollup_mismatch`;
7. the bundle holds the roster of each class of its policy that names one (`roster_absent`):
the figures of a class rest on the keys it enrols;
8. its `classes` are §7.2's, recomputed (`rollup_mismatch`).
### 8.3 Outcome
- **refuse**: the reason and where.
- **accept**, as a JSON object with these members:
- `policies`: for each policy, in order, `link` (its link hash), `seq`, `not_before`,
`not_after` and `in_force_until`, the end of the time it is in force (§4.3) with every
successor the bundle holds, whenever it was issued (a report is held to the end its own
`issued_at` gives, §6.4);
- `key_log`: `null` where no policy names one; otherwise `origin`, `head` (the latest
revision's `revision_hash`) and `revoked`, for each digest of the latest revision's
`revoked_modules`, in its order, `sha256` and `since`, the instant it was revoked from (§4.4);
- `reports`: for each report, in order, `link`, `device` (its key), `class`, `from`, `to`,
`decodes` (its decode entries) and `frames` (its sink-made pictures);
- `rollup`: `null` where the bundle holds none; otherwise `link` and `classes`, its class lines.
Byte strings are in lowercase hexadecimal, and `refuse` is `{"reason", "at"}`, `at` the place
as §8.2 writes it.
### 8.4 A verifier of many bundles
An operator's service checks each report it is sent, in a bundle of its own, against the same few
policies; a verifier of a batch checks many bundles of one operator. Such a verifier **MAY** keep
each policy that has passed step 1's reading, signature and rules, under its link hash, and for a
policy of the same link hash in a later bundle skip those three, taking the policy as it read it.
This is sound because a policy is immutable by its link hash: the hash is over every member of the
record, its signature included (§3), so two policies with one link hash are one record, and whether
a record reads, its signature verifies and it keeps §4.2's rules depends on nothing else. The
verifier **MUST** compute each policy's link hash from the record the bundle holds, after checking
that it has §4.1's members, its `kind` and only values of §3's subset (the hash is over RFC 8785's
text, in which a number outside the subset can share a value's text), and **MUST NOT** keep a
result under any other key: under a policy's `seq`, its signer or its signature, a policy changed
after it was signed would pass as the one verified.
What it keeps is each policy's verdict, never a bundle's. Which policies a bundle holds, the chain
between them, the end of the time each is in force, and so the policy a report may name and the
period it may cover, are the bundle's own, found by step 1 over what the bundle holds: a report
that names a policy outside its bundle's window is `policy_unknown` (§8.1) though the verifier read
that policy before. A verifier that keeps policies gives every bundle the outcome one that keeps
nothing gives it, and the corpus holds both to that (§11).
With rosters (§4.5), the policies a report's bundle holds are a few hundred bytes for any fleet, a
report's proof is at most 32 hashes, and a verifier that keeps its policies reads, for each report,
the report, its proof and the records it names, and nothing else of the fleet: its check reads one
hash more each time the roster doubles, and is otherwise the same with 4000 devices in a policy as
with 100 000. A class that lists its keys costs, for each report, a read of the policy's text in
proportion to its keys before its link hash is known.
## 9. The operation
`wai model verify <bundle>` verifies a bundle (§8) and writes its outcome as JSON, exiting 1 when
it is refused; `wai verify` reads a bundle under a path as it reads a fleet report bundle
(`*.wai-models.json`, or a JSON file whose `kind` is `wai-model-bundle`), and reads a policy, a
device attestation, a run report or a roll-up alone: it reads, its signature verifies and, for a
policy, it keeps §4.2's rules. A record alone is checked against nothing else, and says nothing
of the policy a report names or of the reports a roll-up rests on.
## 10. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
| `policy_invalid` | §4.2, §8.2 step 1 |
| `policy_chain_break` | §4.3, §8.2 step 1 |
| `policy_unknown` | §6.4, §8.2 steps 5 and 6 |
| `policy_not_yet_valid` | §6.4 |
| `policy_expired` | §4.3, §6.4 |
| `key_log_absent` | §8.2 step 2 |
| `device_not_in_class` | §6.4 |
| `model_not_in_policy` | §6.2, §6.3 |
| `model_tier_below_policy` | §6.2, §6.3 |
| `model_revoked` | §4.4, §6.2, §6.3 |
| `presentation_unknown` | §6.3 |
| `wrong_operator` | §8.2 step 6 |
| `rollup_mismatch` | §7.2, §7.3, §8.2 step 6 |
| `roster_mismatch` | §4.5, §8.2 step 1 |
| `roster_absent` | §8.2 step 6 |
A verifier of this extension also raises, with the meaning this text gives them:
`dispatch_tier_refused` (§6.2), which neural-dispatch §7 adds; `outside_period` (§6.2, §6.3,
§8.2 step 6), `record_duplicate` (§6.3, §8.2 steps 3, 4 and 6) and `record_overlap` (§6.4), which
receipt-ledger §10 adds; the key-log reasons of §8.2 step 2, which jwp-receipts §7.7 adds;
`record_malformed`, `bad_key` and `bad_signature`, for what does not read and for its
signature.
## 11. Conformance
[`live-conformance/models/`](../live-conformance/models/) holds the corpus, in one file,
`vectors.json`, written by `wai_model_policy_vectors gen` from fixed seeds: synthetic keys,
digests and instants, dispatch reports written by the reference's own `neural_dispatch`
functions, and presentation statements signed as exact-upscale §6.2 has them. Each vector is a
bundle with the outcome §8.3 gives it:
- **Accepted**: a week of a fleet of set-top boxes enrolled by key and signage players vouched
for by an attestation key, under a policy whose key log revokes one set, with decodes on a
neural processor in floating point, on the CPU's integer interpreter and of DRED's weights; a
sink's upscaled, edited and generated pictures; the revoked set run before its revocation; a
device that ran nothing; a device enrolled and silent; and the roll-up recomputed. A policy
and its successor, with reports under each in its time. A fleet with no key log, and reports
with no roll-up.
- **Refused**: each reason of §10 at least once, each vector a control of one defect: among them
a set outside the policy, a revoked set run after its revocation (by its digest, and by a sealed
set's `inner`), a report past its policy's span and one past its successor's start, a neural
processor's float run claimed at `decode-equivalence`, a roll-up that does not recompute, and a
report with its signature changed; and each reason it reuses.
- **Rosters** (§4.5): a fleet whose class names a roster, its reports each carrying its proof, the
roster held and the roll-up recomputed from it; a successor whose roster drops a device. Refused:
a proof for another device's key, a proof made under the predecessor's roster given under the
successor (a stale root), a device the successor dropped proving its place under the successor
with the proof it had, a proof whose index the tree does not have and one a node short, a
membership beside an attestation, a class that names a roster and lists keys, a roster held that
is no class's, one whose keys are out of order, and a roll-up without its roster.
- **Batches** (§8.4), under `batches`: bundles verified in order by one verifier that keeps the
policies it has read, each with the outcome a verifier that keeps nothing gives it. Among them a
policy changed after signing given after the policy it was signed as, and a report that names a
policy its bundle's window does not hold, after a bundle that held it.
- **Refusals** (§6.1, §7.3): the week's fleet with two sinks' refused decodes in their reports and a
roll-up that states refusals over three refused reports held in the bundle (a device no class
enrols, a changed signature, a set outside the policy), each set line's decodes by tier. Refused:
a refused report stated refused for a reason its checks do not give, one that verifies, one the
bundle does not hold, a class line's count of refused reports, its sum of a refused code and a
set line's tiers that do not recompute, a refused report that is one of the bundle's reports,
one naming a class the policy does not have, one outside the period and one that does not read; a
report's refusals out of order and one by a code the sink's checks do not give; a roll-up stating
refusals with a set line without its tiers; refused reports beside a roll-up that states none,
and without a roll-up.
- **A policy of no class, and the chain a report is held to** (§4.3, §6.4): a successor of no class
ending what a week's policy permitted, a report under the predecessor accepted and one under the
successor refused (`device_not_in_class`); a successor issued after a report whose period runs
past its start, the report accepted; the same report signed after the successor was issued,
refused (`policy_expired`); and the same report dated inside its own period, before the
successor was issued, refused (`record_malformed`): the device's escape from a successor that
§6.1's rule closes.
`wai_model_policy_vectors verify` and the test `tests/model_policy.rs` check that the file
regenerates byte for byte, that `wai-rs` gives every vector its outcome, and every batch its
outcomes both with one verifier that keeps its policies and with a fresh one for each bundle, that
every reason of §10 refuses a vector, and that §10 lists exactly the reference's codes.
`live-conformance/model_policy_verify.py` is the second implementation, written from this text:
it shares no code with `wai-rs`; it verifies signatures with WAI's strict rule written from RFC
8032 (`claim-conformance/_ed25519.py`) and link hashes with BLAKE3 written from its
specification (`claim-conformance/_blake3.py`), reads presentation statements with the
exact-upscale corpus's second implementation (`upscale_verify.py`), reads the key log from
jwp-receipts §7.2 itself, computes rosters' trees and checks proofs from RFC 9162 §2.1, keeps
policies between a batch's bundles as §8.4 allows, gives every vector the same outcome, reason and
place, 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.
## 12. Security and privacy considerations
- **What a report rests on.** A run report is the device's statement of what it ran, and its
`refusals` its statement of what its sink refused (§6.1). A device
that runs a set and leaves the run out of its report is not caught by this verification: a
policy says what may run, and the sink's own refusals (SPEC §3.1 check 5, jwp-receipts §7.9)
are what stop a revoked set at a sink that follows them. A sink that holds the policy itself
refuses a set outside it, revoked or past its span before the decode runs, and keeps for its
report only the decodes that ran ([native-sink](native-sink.md) §14, a page's
[browser-sink](browser-sink.md) §15). A report is checked against the
policy, the key log and the statements the verifier holds, and a verifier that holds a
device's dispatch reports or statements from elsewhere (its own sessions, a player's log)
checks them against the report's.
- **Clocks.** `at` is the device's clock, and a statement's `issued_at` its session's. A device
whose clock runs slow can place a run before a revocation that preceded it. A revocation takes
effect at a sink when the sink holds the revision that lists it (jwp-receipts §7.9, *Kill
latency*); a fleet that needs a revocation to bind reports from an instant publishes the
revision before that instant. A report's `issued_at` is the device's clock too, and decides
which successors bind it (§6.4 step 2): a device whose clock runs slow holds its report to fewer
of them, but only for a period that ended by its clock before the successor was issued (§6.1),
and an operator that ends a set from an instant issues the successor before that instant.
- **Floating point.** A path in floating point holds no tier above its capability's, on a neural
processor or anywhere else (neural-dispatch §3). A policy that requires `decode-equivalence`
for a set is therefore met only by an integer path, and a report that claims it for a float
path is refused, not believed.
- **Rosters.** A proof shows one key to be in the roster the policy signed, as a key listed in
`devices` is shown; it shows nothing of the roster's other keys. That they are distinct and in
order, which `silent` rests on, is checked by a verifier that holds the roster, as one holding a
class's `devices` checks them, and every roll-up's bundle holds it (§8.2 step 6); a verifier
given a report alone takes it on the operator's signature over the root, as it takes which keys
the operator enrolled. A device dropped from a successor's roster proves nothing under the
successor, and its reports under the predecessor are held to the predecessor's time in force
(§6.4), as before. The roster names devices by their keys and nothing else; who holds it is the
operator's choice, and a device needs only its own proof.
- **Reuse.** A verifier that keeps policies between bundles (§8.4) keeps what a fresh verifier
would compute from the same bytes, under a hash of all of them; a result kept under anything
less would accept a changed policy, and one carried from one bundle's window to another's would
accept a report under a policy its bundle does not hold. Both are planted in the corpus.
- **Keys.** The operator key enrols devices and signs roll-ups; one who holds it can enrol any
device and permit any set, so it is kept as fleet §12 keeps an organisation key. An attestation
key vouches for every device it signs for, in every class that names it; a class that names a
maker's key admits every device that maker vouches for. A device key signs its reports and no
one else's.
- **What a report reveals.** A presentation statement is signed under a session's key and names
no device (exact-upscale §10). A run report binds the statements it names to a device key, and
a set's digest can name a title (prior-carriage §18.3): a run report shows which titles a
device presented. It travels to the operator, and from it to those the operator shows its
devices' records to. A roll-up names classes, sets and counts, and no device.