# WAI Extension: Synthesis Provenance > Status: Draft. Registers the record `wai.media.synthesis`: what a piece of media is, for every > kind of media WAI carries. An **origin claim** says of one object, one group or one track that > it is **captured**, **reconstructed** (a codec, neural or not, rebuilding what was captured), > **assistive** (an edit that does not substantially alter its input, such as an upscale or a > denoise) or **synthesized** (generated). The claim names each input by the link hash of the > input's own claim, and its class is the greater of the class its own step gives and its > inputs' classes, so the class rises along every chain of derivations and never falls. The > claim restates the content's IPTC digital source type, which is a function of the claim, and > so is its C2PA statement: the actions and the `c2pa.ai-disclosure` assertions a manifest > carries. An assistive claim carries **evidence**: where its step is one of the two integer > derivations this extension pins, a verifier holding the input's decoded samples recomputes the > output and compares it, so the claim that the edit is assistive is checked, not trusted. The > signed claim is the first of two **marking layers**; the second is a watermark whose payload is > a function of the claim, and a verifier refuses a detection that disagrees with it. The > watermark algorithm itself is a slot this revision leaves to a later one. An **AI participant** > of a room is marked in its grant, and its messages, captions and tracks carry its machine > origin. A **review** is a signed verdict over one exact reconstruction, which a player presents > only for a presentation whose decode path holds the review's tier, so one review holds for > every viewer of that output. A **label policy** says when a label must be shown, and a player's > signed **log** shows whether it was. No new transport. The reference implementation is the > `synthesis` module of `wai-rs` (feature `synthesis`); the corpus is > [`live-conformance/synthesis/`](../live-conformance/synthesis/), and its second > implementation is `live-conformance/synthesis_verify.py`. > Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. > > Texts read for this revision, each pinned to the version named: the Code of Practice on > Transparency of AI-Generated Content, final text of 10 June 2026, Section 1 Measures 1.1 to 1.3 > and Section 2 Measures 1.1 and 1.2 and its Annex 1; the EU icons the Commission publishes for > that Annex, as fetched on 21 October 2026 from the page it last updated on 13 October 2026 > (§10.2); Regulation (EU) 2024/1689, Article 50, paragraphs 2, 4 and 5 as that Code reproduces > them, and the last sentence of paragraph 2 in a reproduction of the Official Journal text (the > Official Journal itself could not be fetched for this revision); the C2PA Technical > Specification, version 2.4, §6.4, §18.15.2, §18.15.4.5 and §18.28; and the IPTC Digital Source > Type vocabulary, whose most recent concept change is dated 2024-10-23. A section number of one > of those texts in this extension is that version's. ## 1. Scope In scope: the records and their encodings (§2); the classes, the steps and the origin claim (§3); the two pinned integer derivations and the evidence that makes an assistive claim checkable (§4); the digital source type (§5) and the C2PA statement (§6) a claim gives; the two marking layers, their records and their agreement (§7); AI participants of rooms (§8); the review and when a player presents it (§9); when a label is shown, the EU icons pinned by digest, and a player's log (§10); verification (§11). Out of scope: a transport; how a generator, a codec or an editor works; the watermark algorithm of the second marking layer and its detector, which a later revision registers in the slot of §7.4; how a manifest is assembled, embedded and signed, which is C2PA's and [provenance](provenance.md)'s; key discovery for the signers of these records, which a verifier takes from [jwp-receipts](jwp-receipts.md) §7 as it does for any other key; and the rendering of a label, beyond the asset it shows and the instants it shows it at. Relationship to the core spec: `wai.media.synthesis` 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 [timed-text](timed-text.md) and [live-events](live-events.md): a caption's `origin` (live-events §4.10), and a chat's or a reaction's (§4.4, §4.5 there), is this extension's class for a text event, with no record of its own: origin 1 is a person's text, class `captured` with source `digitalCreation`; origin 2 a machine's, class `synthesized` generated with oversight `fully_autonomous`; origin 3 a machine's reviewed by a person, class `synthesized` with oversight `human_validated`. Timed-text §10.3's export is then §5's and §6's for such a step. Relationship to [energy-binding](energy-binding.md) §6.1: a decode, and any codec, reconstructs; it does not create, and a neural codec does not make its output AI-generated. This extension keeps that: a codec is a `convert` step (§3.2), which names no model, asserts no source of its own and keeps its input's class, and its C2PA statement is `c2pa.opened` and `c2pa.converted` with no digital source type (§6). Relationship to [determinism-tiers](determinism-tiers.md): a pinned derivation of §4 is integer throughout, so its output is a pure function of its input's samples, and a claim over it is checked by recomputation, at the tier `replicate` promises. A review (§9) is presented only where the presentation holds the review's tier, under the rule of determinism-tiers §2 (*What a tier covers*): a tier covers what the sink emits. Duties this extension supports: the two machine-readable marking layers of the Code of Practice (Section 1, Measure 1.1: Sub-measure 1.1.1, signed and time-stamped metadata; Sub-measure 1.1.2, an imperceptible watermark) and its richer provenance information (Measure 1.3: the model, the timestamp, the type of operations); the disclosure of AI systems that interact with people (Article 50(1)); and the placement of a label at first exposure, after interruptions and at regular intervals (Section 2, Sub-measures 1.2.1 to 1.2.3). WAI states what its records carry and what a verifier checks. It makes no statement about any regime, and a deployment confirms what a regime asks of it against that regime's text. ## 2. Records Every record of this extension is a JSON object (RFC 8259) under the encodings of [jwp-receipts](jwp-receipts.md) §2.4.3: byte strings in lowercase hexadecimal; integers unsigned, at most 2^53 − 1, in plain decimal; no other number; no boolean; a **term** and a **date-time** as defined there. A record is read as jwp-receipts §3.1 step 1 reads a claim: RFC 8259's grammar, no member named twice in one object, at most 32 levels of nesting. Each record has exactly the members its section lists, no member missing and none added, at any level, and a `kind` naming it; one that does not is `record_malformed`. Every record carries `signer_pubkey` (32 bytes, the Ed25519 key of RFC 8032 that signs it) and `sig` (64 bytes): ``` sig = Ed25519(signer_key, "wai:synthesis\x01" || JCS(record without "sig")) link_hash = BLAKE3("wai:synthesis-link\x01" || JCS(record)) ``` JCS is RFC 8785, which on these values reduces to what jwp-receipts §2.4.3 states. 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 wherever another names it. A record whose members carry `issued_at` holds a date-time there, the instant its signer signed it: the time stamp the Code of Practice's Sub-measure 1.1.1 asks for, self-asserted. A digest of samples is SHA-256 (FIPS 180-4) over a canonical buffer: for an image, the `RGB8` buffer of [reconstruction-binding](reconstruction-binding.md) §2 (row-major, three bytes a pixel, no row padding); for another medium, the canonical buffer its capability's `*-equivalence` names ([determinism-tiers](determinism-tiers.md) §2). An object's **content hash** is jwp-receipts §2.1's. ## 3. Classes and the origin claim ### 3.1 The origin claim An origin claim, `kind` `"wai-origin"`, has these members: | member | type | meaning | |---|---|---| | `kind` | string | `"wai-origin"` | | `subject` | object | what it is about: `{"type": "object", "content_hash"}`, an object by its content hash; `{"type": "group", "receipt"}`, a group by its group receipt's link hash (jwp-receipts §3 step 3); or `{"type": "track", "track", "from_group"}`, a track by its digest (live-rooms §6.2: SHA-256 over the rendering of its namespace, one zero byte and its name) from the group `from_group` on | | `media` | string | `audio`, `video`, `image` or `text` | | `class` | string | `captured`, `reconstructed`, `assistive` or `synthesized` (§3.2) | | `step` | string | `capture`, `convert`, `assist` or `generate` (§3.2) | | `operation` | term | what ran: a capability or derivation string, or a term naming the type of operation (`upscale`, `inpainting`, `speech-synthesis`) | | `record` | 32 B hex or `null` | the identity of a record that derived the subject, where its deriver signs one; `null` otherwise | | `source` | string | the subject's digital source type (§5) | | `models` | array | the models the step ran, at most 16, each `{"digest", "type"}`: a prior's digest (SPEC §3.1) and `trained` (a model derived from sampled content) or `algorithmic` (one that is not); no digest twice | | `oversight` | string or `null` | for `generate`: `fully_autonomous`, `prompt_guided` or `human_validated` (C2PA 2.4 §18.28.4); otherwise `null` | | `inputs` | array | the objects the step read, at most 16, each `{"content_hash", "origin", "class", "source"}`: its content hash, the link hash of its own origin claim or `null` where it has none, and that claim's class and source, restated; no content hash twice | | `evidence` | object or `null` | for `assist`: §4.3; otherwise `null` | | `issued_at` | date-time | when the signer signed it | | `signer_pubkey`, `sig` | | §2 | `media`, `class`, `step`, `oversight`, a model's `type` and an input's `class` hold only the values the table names, and `source` and an input's `source` only the codes of §5. Otherwise `record_malformed`. Who signs: the party that performed the step: the capturing device or its operator, the encoder, the editor, the generator's provider. A claim carries its signer's authority and no more; which signers a verifier trusts is its policy. A verifier is given at most one origin claim for one subject; a second for a subject an earlier claim names is `claim_conflict`. ### 3.2 Steps and the class lattice The classes are ordered: `captured` < `reconstructed` < `assistive` < `synthesized`. Each step gives a class, and constrains the claim: | step | class it gives | inputs | models | `oversight` | `evidence` | |---|---|---|---|---|---| | `capture` | `captured` | none | none | `null` | `null` | | `convert` | `reconstructed` | at least one | none | `null` | `null` | | `assist` | `assistive` | exactly one | any | `null` | an object | | `generate` | `synthesized` | any | at least one | not `null` | `null` | A `capture` states as its `source` one of `digitalCapture`, `computationalCapture`, `screenCapture` or `digitalCreation`: a recording of the world, or a person's work made without a generative model. A claim that breaks the table is `record_malformed`. - **capture** records the world, or a person's authorship: a camera, a microphone, a screen recording, a person typing or drawing. - **convert** rebuilds or rearranges its inputs without generating or enhancing anything: an encode, a decode, a transcode, a re-package, a decoder's reconstruction (neural or not), and a derivation that copies samples or composes them (crop, rotation, grid or overlay composition, [derived-tracks](derived-tracks.md)). A codec names no model: its parameter set is pinned in its envelope, and it reconstructs what its input holds. - **assist** edits one input without substantially altering it: an upscale, a denoise, a colour correction. It is the step whose claim needs evidence (§4). - **generate** produces content by a model: a generated picture, voice or video, an inpainting, a voice clone, a talking head, a frame generated between two others, an avatar a model drives. The **class of a claim** is the greatest of its step's class and its inputs' stated classes. A claim whose `class` is another is `class_mismatch`. So a conversion of a synthesized input is synthesized, and a transcode, a clip or a re-package can never lower what its input was. ### 3.3 Checks against what a verifier holds - **Inputs.** For each input whose `origin` names a claim the verifier holds: that claim's subject is the object of the input's `content_hash`, and its `class` and `source` are the input's. Otherwise `input_mismatch`. An input whose claim is not held is taken as stated. - **The subject's own envelope.** Where the subject is an object and the verifier holds its envelope (SPEC §2): an envelope whose capability's registry class is `GenerativeDeterministic` or `Behavioral` (SPEC §7), or whose manifest's `intent` is `create`, is generated by every sink that presents it, and its claim's `step` is `generate`. A claim of another step over such an envelope is `class_understated`. ### 3.4 Through every derivation A party that derives an object from others and publishes it signs an origin claim for it whose inputs name the inputs' claims; where it signs a record of the derivation too, the claim's `record` names that record. A relay forwards claims as jwp-receipts §4.1 has it forward any receipt, byte for byte, and alters none. So each object a sink receives has a claim, and the chain of claims behind it reaches the capture or generation it came from: | derivation | the output's claim | |---|---| | a relay forwards an object | none: the object is the same, and its claim holds for it | | an object re-packaged, or a `WAI2` component exploded (SPEC §7.1) | the same object for every rule that names objects by content hash; its claim holds | | an encode, a transcode, a rung of a ladder, a decoder's reconstruction | `convert` of the input | | a crop, a rotation, a composition of several inputs | `convert` of the inputs | | an upscale, a denoise, a sink-side enhancement | `assist` of the input, with evidence (§4) | | a frame generated between two others, a talking head from a picture and a voice, an inpainting | `generate` over the inputs | | a clip of a recording, a segment of a track | the claim of each group or object it holds, unchanged | A track or group claim covers each object of the track or group; an object claim, where one is given, is the object's. ## 4. Assistive evidence ### 4.1 `wai-synthesis:upscale2` The derivation `wai-synthesis:upscale2`, with no parameters (`{}`), maps a *w* × *h* `RGB8` buffer `in` to a 2*w* × 2*h* buffer `out`. For each pixel (*x*, *y*) of `in` and each of its three bytes, with *x*₁ = min(*x* + 1, *w* − 1), *y*₁ = min(*y* + 1, *h* − 1), *a* = in(*x*, *y*), *b* = in(*x*₁, *y*), *c* = in(*x*, *y*₁) and *d* = in(*x*₁, *y*₁): - out(2*x*, 2*y*) = *a*; - out(2*x* + 1, 2*y*) = (*a* + *b* + 1) >> 1; - out(2*x*, 2*y* + 1) = (*a* + *c* + 1) >> 1; - out(2*x* + 1, 2*y* + 1) = (*a* + *b* + *c* + *d* + 2) >> 2. This is the half-sample interpolation `wai.video.int_motion` predicts with (SPEC §5), in the same integer arithmetic. ### 4.2 `wai-synthesis:denoise3` The derivation `wai-synthesis:denoise3`, with no parameters (`{}`), maps a *w* × *h* `RGB8` buffer `in` to a *w* × *h* buffer `out`: for each pixel (*x*, *y*) and each of its three bytes, out(*x*, *y*) = (8 + Σ *k*(*i*, *j*) · in(clamp(*x* + *i*), clamp(*y* + *j*))) >> 4, the sum over *i*, *j* ∈ {−1, 0, 1}, with *k* = 4 at (0, 0), 2 where exactly one of *i*, *j* is 0, and 1 at the four corners; clamp keeps a coordinate within 0 to *w* − 1 (or *h* − 1). ### 4.3 The evidence An assist claim's `evidence` is an object of exactly these members: | member | type | meaning | |---|---|---| | `tier` | string | `replicate`: the step is a pinned derivation, checkable by recomputation; `attest`: it is not, and the claim asserts that the edit is assistive | | `derivation` | term | the derivation run | | `params` | object | its parameters | | `width`, `height` | integer | the input's size, each at least 1 | | `input_samples` | 32 B hex | the digest of the input's samples (§2) | | `output_samples` | 32 B hex | the digest of the output's samples | `tier` and the sizes outside these values are `record_malformed`. `replicate` evidence names `wai-synthesis:upscale2` or `wai-synthesis:denoise3`, with `params` `{}`, and an output of at most 2^26 bytes (4 · `width` · `height` · 3 for the upscale, `width` · `height` · 3 for the denoise); otherwise `evidence_invalid`. A derivation evaluated in float, or by any operation this revision does not pin, states `attest`: the edit may well be assistive, but its claim can only assert it. A model-driven derivation joins the pinned set when a capability registers it in integer arithmetic at `decode-equivalence`, with its parameter set pinned (SPEC §3.1); its evidence then names it and its pin, and is checked the same way. This revision registers none. ### 4.4 Checking the evidence A verifier is given the decoded samples of the objects it holds (§11.1). For an assist claim, in this order: 1. `replicate` evidence that §4.3 does not admit: `evidence_invalid`. 2. With `replicate` evidence and the input's decoded samples held: they are `width` · `height` · 3 bytes and their digest is `input_samples` (`input_samples_mismatch`); the derivation is run over them, and the digest of its output is `output_samples` (`assistive_recompute_mismatch`). The claim is then **proved**. Without the input's samples it is **not recomputed**; `attest` evidence is **asserted**. 3. With the subject's decoded samples held (an object subject): their digest is `output_samples` (`samples_mismatch`). 4. Under a policy that requires proof (§11.1), a claim that is not proved: `assistive_not_proved`. A proved claim says that this output is that pinned derivation of that input, and nothing else: a fact any verifier holding the input's samples checks, at the tier `replicate` promises. ## 5. The digital source type A claim's `source` is a code of the IPTC Digital Source Type vocabulary, whose terms are `http://cv.iptc.org/newscodes/digitalsourcetype/` followed by the code. It is a function of the claim: | step | inputs | `source` | |---|---|---| | `capture` | none | as stated: `digitalCapture`, `computationalCapture`, `screenCapture` or `digitalCreation` | | `convert` | one | the input's `source` | | `convert` | several | `compositeSynthetic` when an input's class is `synthesized`; else `compositeCapture` when every input's source is `digitalCapture` or `computationalCapture`; else `composite` | | `assist` | one | the input's `source` when its class is `synthesized`; else `algorithmicallyEnhanced` | | `generate` | any | the **generated source**: `algorithmicMedia` when no model is `trained`; else `compositeWithTrainedAlgorithmicMedia` when `oversight` is `human_validated` or an input's class is not `synthesized`; else `trainedAlgorithmicMedia` | A claim whose `source` is another is `source_mismatch`. The codes are the vocabulary's own definitions: `algorithmicallyEnhanced` is an algorithmic modification that leaves the main content unchanged; `trainedAlgorithmicMedia` media created by a model trained on captured content; `compositeWithTrainedAlgorithmicMedia` an edit made with such a model, and, by C2PA 2.4 §18.28.3's pairing, generated content a person reviewed and approved; `algorithmicMedia` media created purely by an algorithm with no sampled training data. A `reconstructed` claim keeps its input's code: a codec makes no claim about what it reconstructed. ## 6. The C2PA statement A claim gives one statement, the JSON object `{"actions": A, "assertions": D}` written in JCS (RFC 8785): - `A`, the actions of the manifest's first actions assertion (C2PA 2.4 §18.15.2): - a claim with no input or with several: `[{"action": "c2pa.created", "digitalSourceType": U}]`, `U` the IPTC term of the claim's `source`; - a `convert` of one input: `[{"action": "c2pa.opened"}, {"action": "c2pa.converted"}]`, with no digital source type, as [energy-binding](energy-binding.md) §6.1 has it for a decode: the input is the parent ingredient, and its own manifest states its source; - an `assist` of one input: `[{"action": "c2pa.opened"}, {"action": "c2pa.edited", "digitalSourceType": U}]`, `U` the term of `algorithmicallyEnhanced`; - a `generate` of one input: the same, `U` the term of the generated source (§5). - `D`, one `c2pa.ai-disclosure` assertion (C2PA 2.4 §18.28) for each model whose `type` is `trained`, in the claim's order, labelled `c2pa.ai-disclosure`, `c2pa.ai-disclosure__1`, … (C2PA 2.4 §6.4): `{"label": L, "data": {"modelType": "c2pa.types.model", "modelIdentifier": "tag:transaction.science,2026:wai/model/" ‖ the digest in lowercase hexadecimal}}`, and, for a `generate`, `"contentProfile": {"humanOversightLevel": O}` in `data`, `O` the claim's `oversight`. A model of type `algorithmic` has none: C2PA 2.4 §18.28.3 attaches the assertion only where a trained model was invoked. The model is named by digest, as timed-text §10.3 names a caption's. Embedding the statement in a manifest, and signing that, is C2PA's and provenance's. A rendition of a playlist whose objects include one of class `synthesized` SHOULD carry the `public.machine-generated` characteristic (`draft-pantos-hls-rfc8216bis-22` §4.4.6.1), as timed-text §10.2 has it for captions. ## 7. Two marking layers ### 7.1 The layers The **first layer** is the origin claim: signed, time-stamped metadata (the Code of Practice's Sub-measure 1.1.1). The **second** is an imperceptible watermark embedded in the content (Sub-measure 1.1.2), whose payload is a function of the claim (§7.2). The Code asks for both on the outputs of generative systems and treats fingerprinting or logging as a supplement that is never sufficient alone (Sub-measure 1.1.3); a content-bound commitment, such as a [reconstruction binding](reconstruction-binding.md) over decoded pixels, is a supplement of that kind here and not the second layer. Under a policy that requires both layers (§11.1), an object claim of class `synthesized` with no marking record naming it (§7.2) is `layer_missing`. ### 7.2 The payload and the marking record A watermark carrying a claim holds an 8-byte **payload**: the code of the claim's class (0 `captured`, 1 `reconstructed`, 2 `assistive`, 3 `synthesized`), then the first seven bytes of the claim's link hash. Read from the content alone, with no metadata, it says the class and points at the claim. Seven bytes find a claim; they do not prove one, and the claim's signature does that. The marking record names the scheme that reads it (§7.4); a signpost in the content naming the detection solution, as the Code's Measure 3.4(c)(ii) describes one, is not part of this revision. The party that embeds the mark signs a **marking** record, `kind` `"wai-marking"`: | member | type | meaning | |---|---|---| | `kind` | string | `"wai-marking"` | | `subject` | 32 B hex | the content hash of the marked object | | `origin` | 32 B hex | the link hash of its origin claim | | `scheme` | term | the watermark scheme (§7.4) | | `scheme_digest` | 32 B hex | the SHA-256 of the scheme's definition | | `payload` | 8 B hex | the payload embedded | | `issued_at` | date-time | when it signed it | | `signer_pubkey`, `sig` | | §2 | A verifier holds the claim the marking names (`claim_unknown`); that claim's subject is the marking's object, and the marking's payload is the claim's (§7.2) (`marking_mismatch`). ### 7.3 Detection and agreement A detector that reads a watermark signs a **detection** record, `kind` `"wai-detection"`: the members of a marking but `origin`, with `payload` the 8 bytes read, or `null` where it found no mark. For each detection, in order, with the origin claim whose subject is the detection's object (if the verifier holds one) and the first marking whose subject it is (if it holds one): 1. With a marking: the detection's `scheme` and `scheme_digest` are the marking's (`detection_mismatch`). 2. With no payload read: where a marking says the object was marked, `watermark_absent`, the second layer having been removed; otherwise the object is **unmarked**. 3. With a payload read and a claim held: the payload is the claim's (§7.2); otherwise `layers_disagree`. A content that carries the mark of a synthesized claim while its metadata says captured, or the mark of another claim, is refused here. With no claim held, the detection is **not checked**. ### 7.4 The scheme slot This revision registers no watermark scheme. A scheme is named by a term `wai-mark:` and the digest of its definition, which a later revision pins with its embedding as an integer derivation appended to a generative one, so that a sink that generates content embeds the mark as part of the reconstruction it is held to, and with its key-free detector. Until then a detection is a signed statement by its detector and nothing more. The conformance corpus names `wai-mark:conformance`, whose definition is the text `wai-mark:conformance names no watermark algorithm (synthesis.md section 7.4)` and which names no algorithm: it exercises the records and their agreement, not a watermark. ## 8. Live rooms ### 8.1 AI participants and their events [live-rooms](live-rooms.md) §4.4 marks an **AI participant**: its grant's `ai` attribute names the model that acts for it. Its `chat`, `reaction` and `caption` events state origin 2 or 3 with that model ([live-events](live-events.md) §4.4, §4.5, §4.10); a room verifier refuses one that does not, `ai_unmarked` (live-rooms §9.2 step 6), and a relay does not forward it. ### 8.2 Its tracks Every track an AI participant publishes is synthesized media. Its publisher **MUST** sign, for each layer track it publishes, a track claim (§3.1, `subject` of type `track`) of class `synthesized`, step `generate`, whose `models` include the model the grant's `ai` names; the claim is signed by the participant's key or a room key. A relay **MUST NOT** forward the objects of such a track while it holds no such claim. A verifier given a room (§11.1) replays its lifecycle events in the merged order (live-rooms §9.2): each `join` and `grant_exchange` sets its subject's grant, each `leave` clears it, and for each `publish` and `takeover` whose subject's grant names a model in `ai`, each layer track *k* from 0 to `layers` − 1, owned by the subject (its key in lowercase hexadecimal) or by the slot (`_` and its name), of the published media kind, is named by its digest (live-rooms §6.2). A track with no claim as above is `ai_track_unlabelled`, at that lifecycle event. ## 9. Certified synthesis ### 9.1 The review A person or an organisation that reviews a synthesized output signs a **review**, `kind` `"wai-review"`: | member | type | meaning | |---|---|---| | `kind` | string | `"wai-review"` | | `subject` | 32 B hex | the content hash of the object reviewed | | `origin` | 32 B hex | the link hash of its origin claim, of class `synthesized` | | `tier` | string | the tier at which the reconstruction reviewed is portable: `decode-equivalence` or `generation-equivalence` ([determinism-tiers](determinism-tiers.md) §2; SPEC §7 `GenerativeDeterministic`) | | `reconstruction` | 32 B hex | the digest of the canonical output the reviewer saw (§2) | | `decoder` | term | the decoder the reviewer's output came from | | `variant` | term or `null` | its decode path, where it has more than one | | `scope` | string | what was reviewed: `accuracy` (words, a script), `rendering` (pixels, a voice, signing), `translation` | | `verdict` | string | `approved`, `approved-with-notes` or `rejected` | | `notes` | 32 B hex or `null` | the digest of the notes, `null` exactly for `approved` | | `role` | term or `null` | a role the reviewer holds for this scope, as the reviewer states it | | `issued_at` | date-time | when it signed it | | `signer_pubkey`, `sig` | | §2 | A verifier holds the claim a review names (`claim_unknown`), whose subject is the review's object and whose class is `synthesized` (`review_subject_mismatch`). ### 9.2 Presenting a review A player **proves its tier** by stating its decode path and the digest of what it presented, in a `present` entry of its log (§10.3): the decoder, its variant, the tier the path holds, and the output's digest. The tier a path holds is the weakest along it: the decoder's tier at its pin (SPEC §3.1), and `none` where any float stage, or any processing after the decode that alters the medium, lies on the path ([determinism-tiers](determinism-tiers.md) §2 *What a tier covers*: a tier covers what the sink emits). A verifier that holds the object's decoded samples (the output of a decode at the stated tier) checks the stated digest against them: `present_output_irreproducible` where it differs. A player **MAY** present a review, in a `review` entry of its log, only for an object it has presented, and only where its latest presentation of the object before that entry: 1. presented the review's object, which the verifier holds the review of (`claim_unknown`, `review_subject_mismatch`); 2. under a review whose verdict is not `rejected` (`review_rejected`); 3. on a path of the review's tier (`review_path_not_exact`): a path of tier `none` or `entropy-consistency` never presents a review made at `decode-equivalence`; 4. with the review's `reconstruction` as its output's digest (`review_reconstruction_mismatch`). Where these hold, the viewer received the output the reviewer saw, byte for byte: one review holds for every viewer whose presentation is exact at that tier, and for no other. ## 10. Label display ### 10.1 When a label is shown A **label policy**, `kind` `"wai-label-policy"`, signed by the party that presents content to viewers, says which content is labelled and how: | member | type | meaning | |---|---|---| | `kind` | string | `"wai-label-policy"` | | `classes` | array | the classes labelled, 1 to 4 of §3.2's, in its order, none twice | | `visual` | 32 B hex | the SHA-256 of the visual label shown with visual media | | `audio` | 32 B hex or `null` | the SHA-256 of the audible label played with audio-only media; `null` for none | | `interval_us` | integer or `null` | the longest a labelled presentation goes without a label, in microseconds of the log's clock; at least 1 when not `null` | | `grace_us` | integer | how long after the instant it is due a label may come | | `issued_at` | date-time | when it signed it | | `signer_pubkey`, `sig` | | §2 | A label is due, for each kind (visual for `video`, `image` and `text`, audible for `audio`): - at **first exposure**: the first presentation of labelled content of that kind in the session (the Code of Practice's Sub-measure 1.2.1(d)); - after each **interruption**: the first presentation of labelled content of that kind after the presentation resumed from an ad break or another interruption (Sub-measure 1.2.2(b); live-ads' breaks end on a `cue` event, live-events §4.3); - at **intervals**: where the policy has one, every presentation of labelled content of that kind that is neither, no longer than the interval after the last label (Sub-measures 1.2.2(b) and 1.2.3). ### 10.2 The EU icons, pinned The Code of Practice's Annex 1 publishes three EU icons, for content fully generated by AI (`AI GENERATED`), for content partially modified by AI (`AI MODIFIED`), and a basic one (`AI`), each in four variations. The Commission publishes them as two archives: SVG (`https://ec.europa.eu/newsroom/dae/redirection/document/129546`, SHA-256 `f76707b69b546677b173975c018528e5e549e12afb05550f32c2f46500ffbefd` as fetched) and PNG (`https://ec.europa.eu/newsroom/dae/redirection/document/129547`, SHA-256 `aedc51656497a2e8f3e42798e5749a929952f5b262bad368ee3057f58c0f52c7` as fetched). Each file is pinned here by the SHA-256 of its bytes: | file | SHA-256 | |---|---| | `LABEL_AI GENERATED_black transparent.svg` | `63d28ab55916b4548edff21d5fbcac065a13e5fb936e9b304ff0bada45f2fac1` | | `LABEL_AI GENERATED_black.svg` | `503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f` | | `LABEL_AI GENERATED_white transparent.svg` | `d19736f6da9d38af3cffce8ba08f3f7658b573e1794c173fcd8b55a5c200ed44` | | `LABEL_AI GENERATED_white.svg` | `10125cdef3fc60a351df72fe1266bb00ea046086921bd1bb20135f0f07b5ed36` | | `LABEL_AI MODIFIED_black transparent.svg` | `9ab09f54c1ccef01799af43794cd93a8af6607ba9ac6b9526fb90a02be8ddf3d` | | `LABEL_AI MODIFIED_black.svg` | `2e7349e5eca4ee78eeef160dfef4545c31850932245373a07c5401e73e6599c0` | | `LABEL_AI MODIFIED_white transparent.svg` | `7ccbce41f9f821a574007b32806ddb6d635ee8bbc5d753ccd05f66f5a3165845` | | `LABEL_AI MODIFIED_white.svg` | `0c72a7d569a1447e2982a843e156bd5f9cb2ab6f201943c6c68f988dc2f1bb6e` | | `LABEL_AI_black transparent.svg` | `f8e20f1dde8c7d95940da08960b1063620812acc9b4ea96eb5ad051f80f67c21` | | `LABEL_AI_black.svg` | `58ffe859a4d74829d397f534a988081bcefb716849c7d3b84fa7a026dd1d257f` | | `LABEL_AI_white transparent.svg` | `52d87cb3f6a191dba24796f745e1eeb80d342883acd1a2e6d856342ad853766c` | | `LABEL_AI_white.svg` | `5b3b94ae67fea55c4f2d013d0b8ed5b876c1533555ed0ed60e688f2f3ccf1a50` | | `LABEL_AI GENERATED_black transparent.png` | `74c8c4ee720c4a3286c3c1b9f216ac9b918ebf449e8c421dde5a40cd216b27e6` | | `LABEL_AI GENERATED_black.png` | `8d0af57bc93ba3797042a4b56db757d85f924d9b0200b75b8842da7be5e402a2` | | `LABEL_AI GENERATED_white transparent.png` | `06c4c7c00738e0e44c35f5927e72842ff7a9dd757e0374335b9cc1ba6bb52199` | | `LABEL_AI GENERATED_white.png` | `f139d8de4b5173f47a777431e7e03d9258d8ea7ee1ee1a062febc7e9fff067fc` | | `LABEL_AI MODIFIED_black transparent.png` | `f48f6c5137834fff87c701eb9a44934ac51c6b5d826b9b5cfead1b88024f8387` | | `LABEL_AI MODIFIED_black.png` | `0f32e4afbe1459baaf425c78373ea541421ecdb61803438892e2fc07df67da8f` | | `LABEL_AI MODIFIED_white transparent.png` | `1f3b435b0bf775a168309c4ede49b23c7fa94b759a72623932e6ccb7bdef6203` | | `LABEL_AI MODIFIED_white.png` | `64241cf4f87eb0837bd234cb63716c7d78f4e9dbbb8866f40464ca4e6bfbfead` | | `LABEL_AI_black transparent.png` | `24da93847c04a5c86c714c6cd018c45331b47df32936b617b3c8476933d97fa9` | | `LABEL_AI_black.png` | `0814d5f04533c118f81aed0d353ea75d1df4171c40b962dba1dfa044a3d84779` | | `LABEL_AI_white transparent.png` | `5b8c743b679a62453a4bc25070b82bb4db33ad9c68f4ef1b6ef7bb562dd3a4e3` | | `LABEL_AI_white.png` | `b9041e416fed455722a3aaa06a5ea0eb376d8a8e11385fdc8e3e8ee6998cdf26` | A policy whose `visual` is one of these digests shows that EU icon; any other digest names an equivalent label, which the Code allows (Section 2, Commitment 1). A player that shows a pinned icon shows exactly those bytes. No audible EU label is published: the Annex says an audio-only icon will be developed, and the Code's audio work begins with a task force. A policy's `audio` is then the digest of the disclaimer or earcon its signer chooses (Sub-measure 1.2.3), until a revision of this table pins a published one. ### 10.3 The player's log A player signs a **log** of a viewing session, one record per entry, `kind` `"wai-player"`, under a key of that session (no identity of the viewer or the device): | member | type | meaning | |---|---|---| | `kind` | string | `"wai-player"` | | `session` | 32 B hex | fresh random bytes of the session, the same in every entry | | `seq` | integer | the entry's position, from 0 | | `prev` | 32 B hex or `null` | the link hash of the entry before it; `null` for entry 0 | | `at` | integer | the session clock at the entry, in microseconds; never below the entry before it's | | `entry` | object | one of the four below | | `signer_pubkey`, `sig` | | §2, the same key in every entry | The entries: - `{"type": "present", "subject", "origin", "class", "media", "tier", "decoder", "variant", "output"}`: the player presented an object from `at` on: its content hash; the link hash of the origin claim it applied, or `null`; that claim's class as it read it, or `null`; the `media` of §3.1; the tier its path holds (`decode-equivalence`, `generation-equivalence`, `entropy-consistency` or `none`, §9.2); its decoder (a term) and variant (a term or `null`), as a `reconstruct-session` claim names them (jwp-receipts §2.4.12); and its output's digest, or `null` where it states none. - `{"type": "shown", "label", "asset", "trigger"}`: the player showed a label: `visual` or `audio`; the SHA-256 of the asset shown; and why, as the player states it: `first-exposure`, `resume` or `interval`. - `{"type": "resume", "after"}`: presentation resumed after an interruption, `ad-break` or `other`. - `{"type": "review", "subject", "review"}`: the player presented the review whose link hash is `review` for the object `subject` (§9.2). A log whose `seq` is not each entry's position, whose `prev` is not the link hash of the entry before, whose `at` goes back, or whose `session` or key changes, is `player_chain_broken`, at the first entry that breaks it. ### 10.4 The obligations Each `present` entry whose `origin` names a claim the verifier holds states that claim's class, for that claim's object (`present_mismatch`). Under a label policy, in this order: 1. Every `shown` entry's asset is the policy's `visual` for a visual label and its `audio` for an audible one (`label_asset_mismatch`). 2. Each `present` entry whose `class` is one the policy labels is a **labelled presentation** at its `at`, *p*, of the label kind its `media` asks for (§10.1). Taking them in the log's order, each kind on its own: - the first of its kind is a **first exposure**: a `shown` entry of that kind is in the log with `at` at most *p* + `grace_us`; - a later one, when a `resume` entry lies between the labelled presentation of its kind before it and it, is a **resume**: a `shown` entry of that kind has `at` from the latest such `resume`'s `at` to *p* + `grace_us`; - any other, under a policy with an interval, is an **interval**: a `shown` entry of that kind has `at` from *p* − `interval_us` to *p* + `grace_us`. One with no such `shown` entry is `label_not_shown`, at its `present` entry. A label not shown at first exposure is caught by the player's own signed log: the log states the presentation and when the label came. ## 11. Verification ### 11.1 Inputs A verifier is given, each in a list: origin claims, marking records, detection records and reviews; a label policy or none; a player's log or none; the envelopes of the objects it holds (each named by its content hash); the decoded samples it holds, each with the content hash of the object decoded; a room or none, as live-rooms §9.1 gives a room verifier its inputs; and a **policy** of two members: | member | values | default | meaning | |---|---|---|---| | `dual_layer` | `when-given`, `required` | `when-given` | `required`: §7.1's rule applies (`layer_missing`) | | `assistive` | `accept-asserted`, `require-proof` | `accept-asserted` | `require-proof`: §4.4 step 4 applies (`assistive_not_proved`) | ### 11.2 Steps The verifier runs these steps and stops at the first refusal, which it reports with the reason and where: a record by its list and index, the label policy, or the room as live-rooms §9.1 states a place in it. 1. **Reading.** Each record, list by list (origin claims, markings, detections, reviews, the label policy, the log's entries), each in order, reads (§2, §3.1, §3.2, §4.3, §7.2, §7.3, §9.1, §10.1, §10.3) and its signature verifies: `record_malformed`, `bad_key`, `bad_signature`. 2. **Origin claims**, in order, each by these checks in order: a subject named before (`claim_conflict`); its class (§3.2, `class_mismatch`); its source (§5, `source_mismatch`); its inputs and its envelope (§3.3: `input_mismatch`, `class_understated`); its evidence (§4.4: `evidence_invalid`, `input_samples_mismatch`, `assistive_recompute_mismatch`, `samples_mismatch`, `assistive_not_proved`). 3. **Marking.** The markings in order (§7.2: `claim_unknown`, `marking_mismatch`); the detections in order (§7.3: `detection_mismatch`, `watermark_absent`, `layers_disagree`); then, under `dual_layer` `required`, the origin claims in order (§7.1, `layer_missing`). 4. **Reviews**, in order (§9.1: `claim_unknown`, `review_subject_mismatch`). 5. **The room.** Live-rooms §9: a refusal there is this verification's, at the room. A gap is not a refusal. Then its AI participants' tracks (§8.2, `ai_track_unlabelled`). 6. **The log.** Its chain (§10.3, `player_chain_broken`); then each entry in order: a `present` entry (§10.4, `present_mismatch`; §9.2, `present_output_irreproducible`) and a `review` entry (§9.2: `claim_unknown`, `review_subject_mismatch`, `review_rejected`, `review_path_not_exact`, `review_reconstruction_mismatch`); then, under a label policy, §10.4's steps 1 and 2 (`label_asset_mismatch`, `label_not_shown`). ### 11.3 What an accepted case states - for each origin claim, in order: its link hash, class and source, its C2PA statement (§6) as written, and for an assist claim whether it is `proved`, `not-recomputed` or `asserted` (§4.4); - for each detection, in order: its object and its result, `agree`, `unmarked` or `not-checked` (§7.3); - for each AI participant's track, in the order §8.2 reaches them: its digest and the claim that labels it; - for each `review` entry of the log: its index and the review's link hash; - for each labelled presentation that had a label due: its index, the trigger (`first-exposure`, `resume` or `interval`) and the index of the first `shown` entry that met it; - the file name of the EU icon the label policy's `visual` is, where it is one (§10.2); - the room's own outcome, `accept` or `gap`, where a room was given. ## 12. Reasons The codes this extension adds, each with where it is raised: | reason | raised at | |---|---| | `record_malformed` | §2, §3.1, §3.2, §4.3, §11.2 step 1 | | `bad_key` | §2 | | `bad_signature` | §2 | | `claim_conflict` | §3.1 | | `class_mismatch` | §3.2 | | `source_mismatch` | §5 | | `input_mismatch` | §3.3 | | `class_understated` | §3.3 | | `evidence_invalid` | §4.3, §4.4 step 1 | | `input_samples_mismatch` | §4.4 step 2 | | `assistive_recompute_mismatch` | §4.4 step 2 | | `samples_mismatch` | §4.4 step 3 | | `assistive_not_proved` | §4.4 step 4 | | `claim_unknown` | §7.2, §9.1, §9.2 | | `marking_mismatch` | §7.2 | | `detection_mismatch` | §7.3 | | `watermark_absent` | §7.3 | | `layers_disagree` | §7.3 | | `layer_missing` | §7.1 | | `ai_track_unlabelled` | §8.2 | | `player_chain_broken` | §10.3 | | `present_mismatch` | §10.4 | | `present_output_irreproducible` | §9.2 | | `review_subject_mismatch` | §9.1, §9.2 | | `review_rejected` | §9.2 | | `review_path_not_exact` | §9.2 | | `review_reconstruction_mismatch` | §9.2 | | `label_asset_mismatch` | §10.4 | | `label_not_shown` | §10.4 | A verifier given a room also raises every code a room verifier raises (live-rooms §11, and the live-events codes it lists), at the room: `ai_unmarked` among them, for an AI participant's event that does not state its machine origin (live-rooms §9.2 step 6). ## 13. Conformance [`live-conformance/synthesis/`](../live-conformance/synthesis/) holds the corpus, in one file, `vectors.json`, written from fixed seeds by `wai_synthesis_vectors gen`. It lists the EU icons of §10.2 and the **vectors**: each a case of §11.1, its outcome, and, accepted, everything §11.3 states; refused, its reason and its place. It has: - **Accepted**: a capture, a neural codec's reconstruction of it, and its two pinned derivations, each proved by recomputation from the reconstruction's decoded samples; a float super-resolution step whose attest evidence is asserted; a generated video, an algorithmic audio generation from a `wai.generate.spectral` envelope, an inpainting a person reviewed, a composite of a capture and the generated video, and a transcode of the video, each synthesized object marked in both layers, a detection that agrees and one that finds the capture unmarked; a room whose AI host states its model in its chat and caption and labels its speech track; and a review of the generated video presented on an exact path, with labels shown at first exposure, after an ad break and within an interval. - **Refused**: each reason of §12 at least once, each a control of one defect: among them a synthesized object labelled captured (`class_understated`), an assistive claim whose recomputation differs (`assistive_recompute_mismatch`), the two marking layers disagreeing (`layers_disagree`), a review presented on a non-exact path (`review_path_not_exact`), an AI participant's unmarked chat (`ai_unmarked`, raised by the room) and a track it publishes with no claim (`ai_track_unlabelled`), and a label not shown at first exposure, after a break, and within the interval (`label_not_shown`). A conforming verifier gives every vector the outcome, the reason, the place and the statements the corpus states. `wai_synthesis_vectors verify` and the test `tests/synthesis.rs` check that the file regenerates byte for byte, that `wai-rs` gives every vector its outcome, that §12 lists exactly the reference's codes, and that §10.2's table pins exactly the reference's icons. `live-conformance/synthesis_verify.py` is the second implementation, written from this text: it shares no code with `wai-rs`; it reads §10.2's pins and §12's codes from this text, verifies each room with the live-rooms corpus's second implementation (`live_rooms_verify.py`), every signature with WAI's strict rule (`claim-conformance/_ed25519.py`) and every link hash with BLAKE3 written from its specification (`claim-conformance/_blake3.py`); and it mutates copies of the corpus and requires every copy to be refused. ## 14. Security and privacy considerations - A claim carries its signer's authority. A class is a statement; what this extension adds is that a class can only rise along a chain of claims (§3.2), that an assistive claim over a pinned derivation is checked by recomputation (§4), that a generator's own envelope cannot be claimed as anything but generated (§3.3), and that the second layer is checked against the first (§7). A synthesized object whose producer signs no claim at all is caught only by the second layer, and only once a watermark scheme is registered (§7.4). - A watermark payload's seven bytes of link hash locate a claim; they are not a commitment, and a mark can be copied into other content. A detection that agrees with a claim says the content carries that claim's mark, not that the content is the claim's subject; the claim's subject is bound by content hash. - Evidence states digests of samples, never samples. A verifier that recomputes holds the input's decoded samples, which it decoded itself. - A player's log is signed under a key of the session and names objects, claims, labels and instants of the session's clock: no viewer, no device and no account. It shows what that key reports, and is not evidence that a person watched. - A review names its reviewer's key and, where given, a role the reviewer states; a role is the reviewer's statement until an authority attests it. - A relay can withhold a claim but cannot alter one. A sink that receives an object without the claim its chain should hold sees the gap in the chain of inputs (§3.4).