Skip to main content

WAI Extension: Model Governance

Mirrored from the canonical text at commit c5e7133e ().

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 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 §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 §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 §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 §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. 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/, 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.

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 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 and its verification (§8).

Out of scope: how a sink obtains a set, which prior-carriage 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 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 §5): a signed record a WAI tool emits and verifies. It names no payload format and is not dispatchable.

Relationship to fleet: 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

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 §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:

membertypemeaning
kindstring"wai-model-policy"
seqinteger0 for the operator’s first policy; one more than the policy it supersedes
supersedes32 B hex or nullthe link hash of the policy it replaces; null exactly when seq is 0
not_before, not_afterlog date-timeits span
key_logstring or nullthe origin whose key log revokes sets for this fleet (§4.4), or null for none
classesarraythe classes (below)
issued_atdate-timewhen the operator signed it
signer_pubkey32 B hexthe operator key
signer_idstring1 to 2048 bytes: the operator key’s signer_id (jwp-receipts §7.1)
sig64 B hex§3

A class:

membertypemeaning
namestringits name (§3)
devicesarray of 32 B hexthe device keys the policy enrols in it
attestersarray of 32 B hexthe attestation keys whose devices are in it (§5)
setsarraythe sets its devices may run, each an entry (below)

An entry:

membertypemeaning
sha25632 B hexthe set’s digest (§2)
capabilitytermfor 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)
rolestringdecode, upscale, enhance or generate
tierstringthe 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:

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.

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).

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:

membertypemeaning
kindstring"wai-model-device"
device32 B hexthe device key
not_before, not_afterlog date-timethe span it holds
issued_atdate-timewhen its signer signed it
signer_pubkey32 B hexthe attestation key
sig64 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:

membertypemeaning
kindstring"wai-model-run"
policy32 B hexthe link hash of the policy the device ran under
classstringthe name (§3) of the class it ran as
attestation32 B hex or nullthe link hash of the device attestation that places it in the class; null when the class enrols its key
from, tolog date-timethe period, from before to
decodesarrayat most 65 536 decode entries (§6.2), in order of at, each at not before the one before it
presentationsarray of 32 B hexat most 4096 link hashes of presentation statements its sessions signed (§6.3), in strictly ascending order
issued_atdate-timewhen it signed it
signer_pubkey32 B hexthe device key
sig64 B hex§3

A report that breaks a 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.

6.2 Decodes

A decode entry:

membertypemeaning
atlog date-timewhen the decode ran, on the device’s clock
setobject{"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
dispatchobjectthe 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 (§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 (policy_expired). A report of a period that runs past a policy’s not_after, or past its successor’s not_before, is refused here.
  3. The device is in the class: the policy has a class of that name, and either attestation is null and the class’s devices holds the report’s signer_pubkey, 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).

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:

membertypemeaning
kindstring"wai-model-rollup"
policy32 B hexthe link hash of the policy every report it rests on names
from, tolog date-timethe period, from before to
reportsarray of 32 B hexthe link hashes of the reports it rests on, at most 65 536, in strictly ascending order
classesarrayone class line for each class of the policy, in the policy’s order
issued_atdate-timewhen the operator signed it
signer_pubkey32 B hexthe operator key
signer_idstringas §4.1
sig64 B hex§3

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). 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:

figurewhat it is
reportsthose reports
devicesthe distinct device keys that signed them
silentthe class’s devices (the keys the policy enrols) that signed none of them
setsone 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:

figurewhat it is
decodesits decode entries (§6.2)
unitsof those, how many ran on each unit
framesits sink-made pictures (§6.3)
pixelsthe sum of their pixels (§6.3)

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.

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. 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.

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).
  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. its classes are §7.2’s, recomputed (rollup_mismatch).

8.3 Outcome

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:

reasonraised 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, §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 and 4) 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/ 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:

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, 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, 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