open-standards/wai/extensions/ai-usage.md
Transaction Science a7157f691e
Some checks failed
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 6s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m46s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m46s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 14s
WAI conformance / does this change touch it (pull_request) Successful in 13s
WAI energy receipts / does this change touch it (pull_request) Successful in 9s
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Successful in 12s
WAI neural sink gates / does this change touch it (pull_request) Successful in 14s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 11s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m37s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 20s
wai-web wasm32 / does this change touch it (pull_request) Successful in 22s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 3m0s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 30s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 10m5s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m31s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 18m15s
wai-pack / wai-pack · linux (pull_request) Successful in 1m47s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 2m38s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 4m13s
wai-quantum studio and desktop / desktop app · macos (pull_request) Has been cancelled
wai-pack / wai-pack · linux-arm64 (pull_request) Has been cancelled
wai-pack / wai-pack · macos (pull_request) Has been cancelled
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been cancelled
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been cancelled
wai-pack / wai-pack · dependency licences (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Has been cancelled
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (pull_request) Has been cancelled
ai-usage §4.1: a manifest that does not read holds no statement that can be read
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 05:43:49 -04:00

545 lines
33 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: AI Usage Preferences and Consumption Claims
> Status: Draft. Registers the record `wai.ai.usage`: AI usage preferences for media, bound to
> the bytes they govern, and the signed statement of an agent that consumed media. A **usage
> statement** carries, verbatim, the preferences of three published vocabularies: the AI usage
> preference vocabulary of `draft-ietf-aipref-vocab-08`, in its exemplary Structured Field form;
> the TDM Reservation Protocol's `tdm-reservation` and `tdm-policy` (Final Community Group
> Report `CG-FINAL-tdmrep-20240510`); and the entries of the `cawg.training-mining` assertion
> (Training and Data Mining Assertion, version 1.1). WAI adds no category to any of them. The
> statement rides in an envelope's manifest, where the object's receipt signs it with the
> content, so it cannot be removed without the object failing its receipt; in a room usage
> record, `WAU1`, that the room authority signs; and in a room grant, as the terms one principal
> is granted. An HTTP origin writes the `Content-Usage` field of `draft-ietf-aipref-attach-05`
> and the TDMRep fields from the signed statement, and a MoQ catalog states it for a track
> before a sink subscribes. One precedence rule decides a use: a grant's terms, then every
> signed statement, the most restrictive governing, then unsigned statements where nothing is
> signed. An agent that consumed objects signs a **consumption claim**, the
> [jwp-receipts](jwp-receipts.md) claim class `consumption`, naming the objects by content hash,
> the term it acted under in each vocabulary, its key as a `did:key`, the statements it saw and
> the grant it held; a publisher verifies it against the objects, and a relay or gateway MAY
> require one before it serves an agent. No new transport. The reference implementation is the
> `ai_usage` module of `wai-rs` (feature `ai_usage`), with the class's body in `claim_receipt`;
> the corpus is [`live-conformance/usage/`](../live-conformance/usage/), and its second
> implementation is `live-conformance/ai_usage_verify.py`.
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
>
> Texts read for this revision, each pinned to the version named:
> `draft-ietf-aipref-vocab-08` (14 September 2026) and `draft-ietf-aipref-attach-05`
> (19 August 2026), Internet-Drafts of the IETF AI Preferences Working Group, both Working
> Group documents intended for Proposed Standard and neither yet submitted to the IESG; the
> vocabulary marks its §3 and §4 as not yet having the group's consensus. RFC 9651
> (Structured Field Values for HTTP). The TDM Reservation Protocol, Final Community Group
> Report of 10 May 2024, the report's latest. The Creator Assertions Working Group's Training
> and Data Mining Assertion, version 1.1, ratified 16 May 2025, the current version. A section
> number of one of these texts is that version's.
## 1. Scope
In scope: the usage statement, its three vocabularies, how each is read and what each says
about a use (§3); where a statement is carried: an envelope, a room, a grant, an HTTP response
and a MoQ catalog (§4); the precedence rule and what a conflict means (§5); the consumption
claim (§6); the gate a relay or gateway applies before serving an agent (§7); and verification
(§8).
Out of scope: whether a preference is followed, which the vocabulary leaves to its recipient
(`draft-ietf-aipref-vocab-08` §3.2), and which this extension makes evidence of, not a duty;
the legal effect of a preference or of a claim; how a crawler or an agent is recognised as one
(§7.1); a profile of the agent protocols that fetch media, which this revision does not define;
and the robots.txt rule of `draft-ietf-aipref-attach-05` §3, which is the origin's and binds no
object (§4.4).
Relationship to the core spec: `wai.ai.usage` 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. The manifest member `ai_usage` (§4.1) is an OPTIONAL member under SPEC §8's rule
for MINOR revisions.
Relationship to [jwp-receipts](jwp-receipts.md): the envelope's statement is signed by the
object's receipt (§2.1 there); the consumption claim is a `claim` receipt of the class
`consumption` (§2.4.4 there), on that extension's envelope, canonical form, signature and
chain, with the body and class rules of §6 here.
Relationship to [live-rooms](live-rooms.md): the room descriptor's layout is unchanged. A
room's statement is a record of its own, chained like the review policy of
[live-moderation](live-moderation.md) §3.4, and a grant states its principal's terms in the
attribute `ai_usage` (live-rooms §4.4).
## 2. Terms
- **Vocabulary**: one of `aipref`, `tdmrep` and `cawg`, the three sources §3 carries.
- **Term**: what a use is named by in a vocabulary: an `aipref` usage category label
(`train-ai`, `ai-use` or `search`, `draft-ietf-aipref-vocab-08` §6.1); the `tdmrep` property
`tdm-reservation`, which governs text and data mining as a whole; or a key of a
`cawg.training-mining` assertion's entries.
- **Preference**: what a vocabulary says about one term: **allow**, **disallow** or
**unknown** (`draft-ietf-aipref-vocab-08` §3).
- **Statement**: a JSON object of §3 holding one or more vocabularies.
- **Signed statement**: one of an envelope whose receipts verify (§4.1), or of a room usage
record that verifies (§4.2).
- **Agent**: a party that consumes media by automated means, for any use a vocabulary names.
An **agent principal** in a room is one whose grant carries the attribute `ai_usage` (§4.3).
- **Content hash**: an object's [jwp-receipts](jwp-receipts.md) §2.1 content hash, 32 bytes,
written as 64 lowercase hexadecimal digits in JSON.
Integers are little-endian. A digest is SHA-256 (FIPS 180-4), 32 bytes. Ed25519 is RFC
8032's, verified strictly ([SPEC](../SPEC.md) §7 *Signatures*). The **canonical form** of a
JSON value is its JCS serialisation (RFC 8785) as jwp-receipts §2.4.3 restricts it: strings,
unsigned integers, `null`, arrays and objects.
## 3. The usage statement
### 3.1 Members
A statement is a JSON object with one or more of these members and no other:
| member | value | source |
|---|---|---|
| `aipref` | an object: `revision`, `value` | `draft-ietf-aipref-vocab-08` |
| `cawg` | an object: `revision`, `entries` | Training and Data Mining Assertion |
| `tdmrep` | an object: `revision`, `tdm-reservation`, `tdm-policy` | the TDM Reservation Protocol |
Each vocabulary keeps its own terms and its own preferences. A statement maps no term of one
onto a term of another, and a WAI tool **MUST NOT** write a term none of them defines.
### 3.2 `aipref`
- `revision`: the string `draft-ietf-aipref-vocab-08`.
- `value`: a statement of preference in the vocabulary's exemplary serialisation
(`draft-ietf-aipref-vocab-08` §6), as the text an HTTP `Content-Usage` field would carry
(`draft-ietf-aipref-attach-05` §2): for example `train-ai=n, search=y`. It is 1 to 1024
characters, each U+0000 to U+007F (§6.3 there: the syntax is ASCII), and it **MUST** parse
as a Dictionary (§3.3).
The preference of a term is read as `draft-ietf-aipref-vocab-08` §6.5 has it: the Dictionary's
member whose key is the term's label, its parameters disregarded, gives **allow** when its value
is the Token `y`, **disallow** when it is the Token `n`, and **unknown** otherwise; a label
absent is unknown, and a key that is not one of the three labels is ignored (§6.4 there). When
a key repeats, the Dictionary keeps its last value (RFC 9651 §4.2.2), so `train-ai=n, train-ai=y`
allows.
### 3.3 The Dictionary
`value` parses as RFC 9651 §4.2 parses a field of type Dictionary: its characters are bytes
(§4.2 step 1, which fails on a byte above 0x7F); leading spaces are discarded; the Dictionary is
parsed by §4.2.2, with its keys (§4.2.3.3), parameters (§4.2.3.2), inner lists (§4.2.1.2) and
every bare item: Integer and Decimal (§4.2.4), String (§4.2.5), Token (§4.2.6), Byte Sequence
(§4.2.7), Boolean (§4.2.8), Date (§4.2.9) and Display String (§4.2.10); leading spaces are
discarded again; and any character left fails. RFC 9651 leaves two checks of a Byte Sequence
to the parser (§4.2.7); here the sequence parses when its content, the characters between its
colons, is characters of the base64 alphabet (RFC 4648 §4) followed by at most two `=`, with
the alphabet characters numbering other than one more than a multiple of four. Neither padding
nor pad bits are checked.
`draft-ietf-aipref-vocab-08` §6.5.1 reads a value that does not parse as unknown for every
term. A statement does not carry one: a signer whose preference would be read as unknown
throughout has made a mistake that binding the statement would preserve, so it is refused.
### 3.4 `cawg`
- `revision`: the string `1.1`.
- `entries`: an object of 1 to 32 members, the assertion's `entries` map
(Training and Data Mining Assertion 1.1 §3.2), member for member. Each key is 1 to 255 bytes
of components separated by single `.`, a component being one or more of `a`–`z`, `A`–`Z`,
`0`–`9`, `_` and `-`; a key beginning `cawg.` is one of the four the assertion defines,
`cawg.data_mining`, `cawg.ai_inference`, `cawg.ai_generative_training` and `cawg.ai_training`,
since it reserves the prefix (§3.1 there). Each value is an object holding `use`, one of
`allowed`, `notAllowed` and `constrained`, and optionally `constraint_info`, 1 to 1024 bytes
of UTF-8 (the member the assertion's CDDL names; its prose spells it `constraints_info`).
A term is a key: `allowed` is **allow**; `notAllowed` is **disallow**; `constrained` is
**disallow**, since the assertion treats it as `notAllowed` in the absence of additional
information (§3.1 there), which a grant can give (§5.2); a key absent is **unknown**. The
assertion's `metadata` map is not carried.
### 3.5 `tdmrep`
- `revision`: the string `CG-FINAL-tdmrep-20240510`.
- `tdm-reservation`: the integer 1, TDM rights are reserved, or 0, they are not (the
protocol's §5.1). The protocol treats any other value as unset; a statement carries none.
- `tdm-policy`: `null`, or the URL of the rightsholder's TDM policy (the protocol's §5.2): 8 to
2048 bytes of `!` to `~` (0x21–0x7E), beginning `https://` or `http://`.
The one term is `tdm-reservation`: 1 is **disallow** and 0 is **allow**. `tdm-policy` is
carried for whoever seeks a licence and is not evaluated.
### 3.6 Reading a statement
A statement is read from JSON in which no object names a member twice. It is refused at the
first of these that holds:
1. It is not an object, it has no member, it has a member §3.1 does not list, or a
vocabulary's object has a member absent, a member its section does not list, or a member
of another type or outside its range: `usage_malformed`. A `revision` that is a string is
in range here; in `aipref`, a `value` that does not parse (§3.3) is not.
2. A vocabulary's `revision` is other than the string its section names:
`usage_revision_unknown`.
3. Its canonical form is longer than 4096 bytes: `usage_malformed`.
So a statement with a malformed `tdmrep` and an unknown `aipref` revision is refused
`usage_malformed`. The **digest** of a statement is SHA-256 over its canonical form.
## 4. Carriage
### 4.1 The envelope: the manifest member `ai_usage`
A `WAI1` or `WAI2` manifest ([SPEC](../SPEC.md) §3, §7.1) MAY carry, at its top level, the
member `ai_usage`, a statement. It applies to the object: every rendition and companion of a
`WAI2`. A reader of an earlier revision ignores it (SPEC §8); a malformed one leaves the
manifest readable, and this extension refuses the statement, not the envelope.
The member is inside the envelope's bytes, and the object's content hash covers them
([jwp-receipts](jwp-receipts.md) §2.1): the group receipt that includes the object signs the
statement with the content. An object whose statement is altered, added or removed no longer
hashes to the content hash its receipt names, and fails its receipt. A verifier given an
envelope and its receipts:
1. accepts the receipts as jwp-receipts §3 steps 1 to 5 do, the envelope hashing to the
object receipt's `content_hash` excepted; a refusal there is `receipt_invalid`;
2. requires the envelope to hash to that `content_hash`: otherwise `usage_unbound`;
3. reads the manifest's `ai_usage`, where present, by §3.6. A manifest that does not read
(SPEC §2) holds no statement that can be read: `usage_malformed`.
An object that passes all three is **held**, with its statement **signed by** the group
receipt's `signer_pubkey`, or with none. A publisher that states a preference for an object writes the
member before it receipts the object. An envelope that carries no receipt carries an unsigned
statement, which §5.3 reads as it reads an HTTP field.
### 4.2 A room: the room usage record `WAU1`
The room authority states the room's preference in a **room usage record**:
| field | size | meaning |
|---|---|---|
| `magic` | 4 | `WAU1` (ASCII) |
| `version` | 1 | 1 in this revision |
| `room` | 32 | the room id (live-rooms §3.2) |
| `prev` | 32 | the digest of the record this one replaces; 32 zero bytes for the first |
| `effective` | 8 | u64: the room-clock instant from which it applies; 0 for the first |
| `statement` | 2 + *n* | a u16 length, then the statement's canonical form, 2 to 4096 bytes |
| `sig` | 64 | Ed25519 by the room descriptor's `authority` over the 18 bytes `wai:ai-usage-room` (ASCII) and 0x01, followed by every byte from `magic` through `statement` |
The record's **digest** is SHA-256 over the bytes from `magic` through `statement`. A reader
reads exactly the bytes it is given, in the order above, and refuses at the first of these that
holds: fewer than 4 bytes, `truncated`; a magic other than `WAU1`, `malformed`; no byte after
the magic, `truncated`; a version other than 1, `unknown_version`; the bytes end before a field,
`truncated`; bytes after `sig`, `malformed`. It then reads `statement` as JSON and by §3.6
(`usage_malformed`, `usage_revision_unknown`), and refuses bytes that are not the canonical form
of what they hold (`usage_malformed`). Then `room` is the room id (`wrong_room`), and the
signature verifies under the authority (`bad_key`, `bad_signature`).
A verifier is given a room's records in a list. The first has `prev` zero and `effective` 0;
each later one has as `prev` the digest of the one before it, and an `effective` above that
one's. A record that breaks this is refused `room_usage_unlinked`. The record **in effect** at
room-clock instant *t* is the last whose `effective` is at or below *t*. A record is replaced,
never amended: as `draft-ietf-aipref-attach-05` §3.3 has it for robots.txt, a change applies
from its instant on and never to what was published before.
An object is the room's when it is given with the room (§8). It is governed at its manifest's
`live.t` ([live-ingest](live-ingest.md) §6), an integer; an object of the room whose manifest
has none is governed by every record in the list.
### 4.3 A grant's terms: the attribute `ai_usage`
A room grant MAY carry the attribute `ai_usage` (live-rooms §4.4), whose value is the canonical
form of a statement: the **terms** the authority grants that principal. They are an
arrangement in the sense of `draft-ietf-aipref-vocab-08` §5.2, which overrides a statement of
preference for the uses it names. A grant whose attribute does not read by §3.6, or is not the
canonical form of what it holds, is refused `grant_usage_malformed`, and its principal holds no
terms. The attribute is signed with the grant (live-rooms §4.2), and the 1024 bytes the grant's
attributes take bound the terms.
### 4.4 HTTP: fields mapped from the signed statement
An origin that serves an object ([http-delivery](http-delivery.md) §5) whose envelope carries
a statement **SHOULD** send, for a `GET` or `HEAD` of it:
- for `aipref`: `Content-Usage:` and the statement's `value`, byte for byte
(`draft-ietf-aipref-attach-05` §2);
- for `tdmrep`: `tdm-reservation:` and `0` or `1`, and, where `tdm-policy` is not `null`,
`tdm-policy:` and the URL (the TDM Reservation Protocol §6.2).
`cawg` has no HTTP field. These fields are unsigned. A client reads them as the response's
statement and compares it with the object's signed statement:
- `Content-Usage` field lines are combined as RFC 9110 §5.3 combines them, with a comma and a
space, and parsed by §3.3. It **conflicts** when it does not parse, or when it gives any of
the three labels a preference other than the signed `value` gives it. Parameters, unknown keys
and spelling (`train-ai=n,search=y` against `train-ai=n, search=y`) do not conflict.
- `tdm-reservation` conflicts when more than one field line carries it, or when its value,
without leading and trailing spaces and tabs, is other than the signed one written in decimal.
`tdm-policy` conflicts when more than one field line carries it, or when its value, trimmed the
same way, is other than the signed URL, or the signed one is `null`. An absent field conflicts
with nothing.
- A field for a vocabulary the signed statement does not carry, or any field for an object with
no signed statement, conflicts with nothing: it is an unsigned statement (§5.3).
A response whose fields conflict is refused `header_conflicts`. Field names compare without
regard to case. The robots.txt `Content-Usage` rule (`draft-ietf-aipref-attach-05` §3) states a
preference for a path, not for an object, and is unsigned; a WAI origin MAY write one, and this
extension reads none.
### 4.5 MoQ: the catalog field `wai_ai_usage`
A WAI track's catalog entry MAY carry `wai_ai_usage`, a statement
([moq-streaming-format](moq-streaming-format.md) §4), so that an agent knows the preference
before it subscribes. A catalog is unsigned. Its statement **MUST** be the statement of every
object on the track that carries one; a catalog whose statement does not read by §3.6, or whose
digest differs from the digest of a held object's statement on the track, is refused
`catalog_conflicts`. A room track's catalog **SHOULD** state the room record in effect when the
catalog is published.
## 5. Precedence
### 5.1 What governs an object
An object's **governing statements** are its signed statements: the statement of its envelope,
when it is held (§4.1); and, when it is a room's, the room record in effect at its `live.t`, or
every record of the room where it has none (§4.2). A principal's **terms** are those of the grant
it acts under (§4.3).
### 5.2 The rule
The preference of a use, named by a term *T* of a vocabulary *V*, for an object and a principal:
1. **Terms.** When the principal's terms carry *V* and give *T* allow or disallow, that is the
preference: an arrangement overrides a statement of preference
(`draft-ietf-aipref-vocab-08` §5.2).
2. **Signed statements.** Otherwise, of the governing statements that carry *V*: disallow when
any gives *T* disallow; else allow when any gives it allow; else unknown. This is the
vocabulary's own rule for combining statements (§5.1 there), the most restrictive applying.
3. **Unsigned statements.** When no governing statement carries *V*, an agent MAY read an
unsigned statement for *V* (an HTTP field, a catalog, an envelope without receipts) by the
same rule. It names no declaring party (§3.1 there), and no verifier can check what it said.
A use named in several vocabularies is disallowed when any of them disallows it; allowed when
none disallows it and one allows it; unknown otherwise. A use that falls under no term of a
vocabulary is **outside** that vocabulary, which then says nothing about it. What an agent does
with an unknown preference is not this extension's: the vocabulary takes no position on a
default (§5 there), and a gate MAY (§7.1).
### 5.3 Conflicts
A signed statement governs over every unsigned one. An unsigned statement that differs from a
signed one is not the signer's: a client that finds one ignores it in step 3, reports the
response or catalog refused (`header_conflicts`, `catalog_conflicts`), and decides by the signed
statement. A stricter unsigned statement is the preference of a party nothing identifies; an
agent MAY honour it as well, and a consumption claim is checked against the signed statements
alone. Two signed statements that differ are not a conflict: rule 2 applies, and the most
restrictive governs.
## 6. The consumption claim
### 6.1 The body
A consumption claim is a [jwp-receipts](jwp-receipts.md) §2.4.3 `claim` of `class`
`consumption`, `subject_type` `object`, signed by the agent's key, whose `subject` is the first
of `objects`. Its `signer_id` is the `did:key` of `signer_pubkey` (jwp-receipts §7.1), the key
that traces the agent. Its body has exactly these members, read in this order at jwp-receipts
§3.1 step 2:
| member | type | meaning |
|---|---|---|
| `objects` | array of 1 to 1024 32 B hex | the content hashes of the objects consumed |
| `usage` | array of 1 to 3 entries | the use, in each vocabulary the agent answers |
| `seen` | array of 0 to 64 32 B hex | the digests of the statements that governed the objects (§5.1) |
| `grant` | 32 B hex or `null` | the digest of the room grant the agent consumed under (live-rooms §4.2) |
A `usage` entry has exactly `vocabulary`, one of `aipref`, `cawg` and `tdmrep`, and `term`, a
string of 1 to 255 bytes or `null`, read in that order. A term names the use as that vocabulary
names it; `null` states that the use falls under no term of the vocabulary. An array of another
length, a `vocabulary` outside the three and a `term` of another length are `field_type`.
### 6.2 Class rules
At jwp-receipts §3.1 step 4, in this order:
1. `objects` is in strictly ascending order: `consumption_objects_unordered`.
2. `subject` is the first of `objects`: `consumption_subject_mismatch`.
3. `usage` is in strictly ascending order of `vocabulary`: `consumption_usage_unordered`.
4. Each `term` that is not `null` is a term of its vocabulary: for `aipref` one of `train-ai`,
`ai-use` and `search`; for `tdmrep`, `tdm-reservation`; for `cawg`, one of the assertion's
four keys or a key of §3.4's form that does not begin `cawg.`:
`consumption_term_unknown`.
5. `seen` is in strictly ascending order: `consumption_seen_unordered`.
6. `signer_id` is the `did:key` of `signer_pubkey`: `consumption_signer_not_principal`.
### 6.3 Context
At jwp-receipts §3.1 step 6 a verifier is given the objects it holds, with their receipts
(§4.1), and, where the claim names a grant or its objects are a room's, the room: its
descriptor, its grants and its usage records. It refuses at the first of these that fails:
1. Each of `objects` is the content hash of an object it holds: `consumption_object_unknown`.
2. When `grant` is not `null`: it is the digest of one of the room's grants that live-rooms
§4.3 accepts, whose `principal` is `signer_pubkey`, and which is good at the `live.t` of
every object of the room the claim names that has one; and every object the claim names is
the room's. Otherwise `grant_mismatch`. A grant refused `grant_usage_malformed` holds no
terms and is still the principal's.
3. `seen` is exactly the digests of the objects' governing statements: `preference_mismatch`.
4. `usage` answers every vocabulary any governing statement carries: `usage_unanswered`.
5. For each object in the order of `objects`, and each entry in the order of `usage`, the
preference of §5.2, with the grant's terms where `grant` names one, is not disallow:
`usage_disallowed`.
A room whose usage records are refused (§4.2) states no preference: its objects are governed
by their own statements alone. A grant the room refuses (live-rooms §4.3) is not one of its
grants, and a claim that names it is refused at step 2.
### 6.4 What an accepted claim states
An accepted claim states, under the agent's key, that the agent consumed those objects for that
use, having seen those statements, under that grant. A verifier reports, for each object, the
preference §5.2 gives the claimed use: **allowed**, **unknown**, or **outside** when every term
is `null`. A claim refused `usage_disallowed` is still its signer's statement: its signature and
chain hold, and what it records is a use the governing statements disallow. A refusal at step 6
is of the use, never of the signature. The publisher, the agent and any party that holds the
objects and the claim reach the same verdict.
## 7. The gate
### 7.1 Policy
A relay or a gateway MAY require consumption claims before it serves an agent. Whether it does,
and how it recognises an agent outside a room, is its own policy; in a room, an agent principal
is one whose grant carries terms (§4.3). The policy has two parameters:
- `window`, 0 to 16: how many of the most recent groups the gate served the agent may lack a
claim. With 0, every object the agent requests is claimed before it is served, as over
HTTP, where the agent names each object by its content hash before it fetches it
([http-delivery](http-delivery.md) §5.2). With *n* ≥ 1, the agent claims what it received
after it received it, as over MOQT, where a live object's content hash is known only once it
is published.
- `require_allowed`: whether a use whose preference is unknown or outside is refused.
### 7.2 Decision
The gate holds the claims the agent gave it, each verified (§8) and signed by the agent's key
(in a room, its grant's principal; elsewhere, the key the gateway authenticates it by): an
accepted claim, or one refused `usage_disallowed`, is held; any other is not. It keeps the groups it served the agent,
in order, a group over HTTP being one object. For a request, a group of objects, the objects it
checks are the requested ones when `window` is 0, and otherwise those of every served group but
the last `window`. For each of those objects, in that order, it refuses at the first of these:
1. No held claim names it: `claim_required`.
2. A held claim that names it was refused `usage_disallowed`: `usage_disallowed`.
3. With `require_allowed`, a held claim that names it gives it a preference other than
allowed: `usage_not_granted`.
Otherwise it serves the request. A gate that refuses tells the agent the reason; over MOQT a
relay ends the subscription, and over HTTP a gateway answers `403`.
### 7.3 Over HTTP
An agent gives a gateway a claim with its request: the field `WAI-Usage-Claim:` and the claim's
canonical form in base64url without padding (RFC 4648 §5). The gateway verifies it, holds it as
§7.2 says, and decides. `WAI-Usage-Claim` is proposed for the HTTP Field Name Registry (RFC 9110
§16.3), and is not registered. This revision defines no MOQT carriage for a claim; a relay that
gates over MOQT holds the claims its agents gave it over HTTP.
## 8. Verification
A verifier is given what each check reads, and nothing more:
| check | inputs | outcome |
|---|---|---|
| a statement (§3.6) | its JSON | accepted with its digest and every term's preference, or refused |
| an object (§4.1) | its envelope, object receipt and group receipt | held, with its statement or none, or refused |
| a room (§4.2, §4.3) | its descriptor, grants and usage records | each record and grant accepted or refused |
| a response (§4.4) | an object and its response fields | consistent, or `header_conflicts` |
| a catalog (§4.5) | a catalog entry and the objects of its track | consistent, or refused |
| a claim (§6) | the claim, the claim before it of its signer, its objects, and its room | accepted with each object's preference, or refused at a step, with its reason |
| a gate (§7) | the policy, the served groups, the claims given, and a request | served, or refused with its reason |
A room descriptor or a grant that does not verify is refused as live-rooms §3.4 and §4.3
refuse it; a room with no verified descriptor governs nothing.
## 9. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
| `usage_malformed` | §3.6, §4.2 |
| `usage_revision_unknown` | §3.6 |
| `receipt_invalid` | §4.1 step 1 |
| `usage_unbound` | §4.1 step 2 |
| `room_usage_unlinked` | §4.2 |
| `grant_usage_malformed` | §4.3 |
| `header_conflicts` | §4.4 |
| `catalog_conflicts` | §4.5 |
| `consumption_objects_unordered` | §6.2 rule 1 |
| `consumption_subject_mismatch` | §6.2 rule 2 |
| `consumption_usage_unordered` | §6.2 rule 3 |
| `consumption_term_unknown` | §6.2 rule 4 |
| `consumption_seen_unordered` | §6.2 rule 5 |
| `consumption_signer_not_principal` | §6.2 rule 6 |
| `consumption_object_unknown` | §6.3 step 1 |
| `grant_mismatch` | §6.3 step 2 |
| `preference_mismatch` | §6.3 step 3 |
| `usage_unanswered` | §6.3 step 4 |
| `usage_disallowed` | §6.3 step 5, §7.2 |
| `claim_required` | §7.2 |
| `usage_not_granted` | §7.2 |
A room usage record also raises `truncated`, `malformed`, `unknown_version`, `wrong_room`,
`bad_key` and `bad_signature`, as live-rooms §3.4 names them, and a claim every code of
jwp-receipts §3.1 steps 1 to 5.
## 10. Conformance
[`live-conformance/usage/`](../live-conformance/usage/) holds the corpus, written from fixed
seeds by `wai_ai_usage_vectors gen` as `vectors.json`. It has:
- **Statements**: each vocabulary alone and the three together, with every term's preference;
`aipref` values that exercise the Dictionary: a repeated key, parameters, a value that is not
a Token, an inner list, a key that is not a label, and every kind of bare item; and one
statement refused for each defect of §3.6, among them a value that does not parse and an
upper-case key.
- **Objects**: a publication whose envelopes carry statements under one group receipt, among
them a `WAI2`; the same envelope with its statement removed (`usage_unbound`), and altered
(`usage_unbound`); one under a receipt whose signature fails (`receipt_invalid`); and one whose
statement does not read.
- **Rooms**: a room with two chained usage records and the grants of two agent principals, one
of whose terms lift a disallow; a record of another room, one whose signature fails, one out of
its chain, and one whose statement does not read; a grant whose terms do not read.
- **Responses** and **catalogs**: fields that agree with the signed statement in another
spelling, and fields and a catalog that contradict it.
- **Claims**: claims accepted with each object's preference — allowed, unknown, outside, and
allowed under a grant over a room's disallow — and one refused for each reason of §6.2 and
§6.3, among them a use the statement forbids, objects the verifier does not hold, and a
signature that fails.
- **Gates**: requests served and refused under each policy, for each reason of §7.2.
A conforming verifier gives every case the outcome, the reason, the step and everything else
the corpus states. `wai_ai_usage_vectors verify` and the test `tests/ai_usage.rs` check that the
file regenerates byte for byte, that `wai-rs` gives every case its outcome, that every reason of
§9 appears, and that §9 lists exactly the reference's codes.
`live-conformance/ai_usage_verify.py` is the second implementation, written from this text: it
shares no code with `wai-rs`; it reads rooms with the live-rooms corpus's second implementation
(`live_rooms_verify.py`), checks every signature with WAI's strict rule
(`claim-conformance/_ed25519.py`) and computes BLAKE3 with `claim-conformance/_blake3.py`, and
parses Structured Fields, canonical forms, receipts and claims itself; and it mutates copies of
the corpus and requires every copy to fail.
## 11. Security and privacy considerations
- A preference is not a security mechanism (`draft-ietf-aipref-vocab-08` §8). What this
extension adds is evidence: the publisher's statement bound to the bytes, and the agent's
statement of what it did, both signed. Neither makes a use happen or stops one.
- Removing an envelope's statement changes its content hash. An object served without its
receipts carries an unsigned statement at most, and an agent that requires receipts sees
the difference. A party that strips the statement and receipts the object under its own key
becomes that object's publisher, under its own key.
- An HTTP field and a catalog are written by whoever serves them. A signed statement governs
over both (§5.3), so an intermediary that rewrites a field changes nothing a claim is
checked against, and a client that compares them sees the rewrite.
- A consumption claim is signed by the agent's key and names the agent by that key alone
(jwp-receipts §7.1). A key-log confirmation, where the agent's origin publishes one, says
who holds the key; without one the claim is the key's statement.
- A claim lists the objects an agent consumed. A publisher that holds it learns what the agent
read and for what use, which is its purpose; an agent that claims under a gate discloses
that to the gate's operator too.
- A claim records the use its agent states. A false statement is a signed false statement:
a verifier checks it against the objects and the statements, and cannot check what the agent
did afterwards.
- `tdm-policy` is a URL a statement carries and a verifier never fetches.