diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index a30972c29..94203ac85 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -114,6 +114,10 @@ on: # reports and its §4 the derivations, edge_verify.py reads both tables, and the # registry's wire identifiers test reads its sections. - 'wai/extensions/deriving-edge.md' + # tests/synthesis.rs requires this extension's §12 to list the codes the reference + # reports and its §10.2 table to pin the reference's icons, synthesis_verify.py reads + # both, and the registry's wire identifiers test reads its sections. + - 'wai/extensions/synthesis.md' - 'wai/wai-web/**' # The audio provenance check below reads the corpora above with this tool. - 'wai/tools/wai_audio_provenance_check.py' @@ -785,7 +789,8 @@ jobs: # energy-steering corpus), and live_moderation_verify.py (the live-moderation corpus), and # e2ee_verify.py (the end-to-end encrypted rooms corpus), live_watermark_verify.py (the # forensic-watermark corpus), live_ads_verify.py (the live-ads corpus), c2pa_live_verify.py - # (the live-provenance corpus) and edge_verify.py (the deriving-edge corpus). + # (the live-provenance corpus), edge_verify.py (the deriving-edge corpus) and + # synthesis_verify.py (the synthesis corpus). # Each exits non-zero on any failure or any difference, and the step fails with it. # One platform: what they check is the reading of the text, not the architecture. independent-verifiers: @@ -1121,6 +1126,26 @@ jobs: (cd wai-rs && cargo test --no-default-features --features live_provenance --lib live_provenance::) (cd wai-rs && cargo test --no-default-features --features live_provenance --test live_provenance) python3 -I live-conformance/c2pa_live_verify.py live-conformance/c2pa-live + # wai.media.synthesis (extensions/synthesis.md): the reference module's unit tests (the + # pinned derivations by their rules, the class lattice, a generator's envelope) and + # tests/synthesis.rs (the committed corpus regenerates byte for byte from its fixed seeds + # and no other file is there, wai-rs gives every case its stated outcome and statements, + # §12 lists exactly the reference's codes, each of which the corpus gives, and §10.2's + # table pins exactly the reference's icons); then the corpus's second implementation, + # synthesis_verify.py, written from the extension's text and sharing no code with wai-rs, + # which runs the pinned derivations and writes each digital source type and C2PA + # statement itself, verifies each room with live_rooms_verify.py and every record with + # WAI's strict Ed25519 rule and BLAKE3 from claim-conformance, reads the extension's icon + # and reason tables, and requires every mutated copy of the corpus to fail; standard + # library only, run isolated (-I). + - name: synthesis provenance · wai-rs and synthesis_verify.py + working-directory: wai + shell: bash + run: | + set -euo pipefail + (cd wai-rs && cargo test --no-default-features --features synthesis --lib synthesis::) + (cd wai-rs && cargo test --no-default-features --features synthesis --test synthesis) + python3 -I live-conformance/synthesis_verify.py live-conformance/synthesis # wai.live.e2ee (extensions/confidentiality.md): the corpus's second implementation, # e2ee_verify.py, written from the extension's text and the documents it reads and # sharing no code with the reference (joule-moq's secure_objects and joule-moq-mls, diff --git a/joule-code/joule-code-rs/crates/joule-moq-ingest/src/room.rs b/joule-code/joule-code-rs/crates/joule-moq-ingest/src/room.rs index 4091edf32..ce3c4588f 100644 --- a/joule-code/joule-code-rs/crates/joule-moq-ingest/src/room.rs +++ b/joule-code/joule-code-rs/crates/joule-moq-ingest/src/room.rs @@ -1021,6 +1021,11 @@ impl Grant { return bad; } let Some(attrs) = read_attrs(&attrs) else { return bad }; + // live-rooms §4.4: `ai`, marking an AI participant, is a model's digest as 64 lowercase + // hexadecimal digits. + if attrs.iter().any(|(k, v)| k == "ai" && !(v.len() == 64 && v.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)))) { + return bad; + } // Step 6. if exp - nbf > MAX_GRANT_S { return Err(Reason::GrantTooLong); diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index c6d2f8dd3..8b9492cdc 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,59 @@ wire format may change and such changes are recorded here. ## v0.4.0 — current +### `wai.media.synthesis`: what any piece of media is, in two marking layers, with reviews and labels + +- **The extension** ([synthesis](extensions/synthesis.md)) registers the record + `wai.media.synthesis`. An **origin claim** (`wai-origin`) says of one object, group or track + that it is captured, reconstructed (a codec, neural or not, rebuilding what was captured), + assistive (an edit that does not substantially alter its input) or synthesized (generated). + It names its step (`capture`, `convert`, `assist`, `generate`), the models the step ran, the + oversight of a generation, and each input by its content hash and the link hash of its own + claim. Its class is the greatest of its step's and its inputs', so it rises along a chain of + derivations and never falls; a transcode of a generated video is synthesized. A codec is a + conversion: it names no model and keeps its input's class and source, as energy-binding §6.1 + has a decode. +- **IPTC and C2PA as functions of the claim.** The digital source type (`digitalCapture`, + `algorithmicallyEnhanced`, `trainedAlgorithmicMedia`, `compositeWithTrainedAlgorithmicMedia`, + `compositeSynthetic` and the rest) follows from the step, the inputs, the models and the + oversight; the C2PA statement is the actions (`c2pa.created` with the source, or + `c2pa.opened` then `c2pa.converted` or `c2pa.edited`) and one `c2pa.ai-disclosure` per trained + model, written in JCS. The mapping keeps timed-text's for captions. +- **Assistive claims checked, not trusted.** Two integer derivations are pinned: a 2× upscale + with `wai.video.int_motion`'s half-sample rounding, and a 3 × 3 binomial denoise. Evidence + names the input's and the output's sample digests; a verifier holding the input's decoded + samples recomputes the output and refuses a mismatch. A float or unpinned step states + `attest` and is reported as asserted; a policy can require proof. +- **Two marking layers.** The signed claim is the first; the second is a watermark whose 8-byte + payload is the claim's class and the head of its link hash. A marking record names the scheme + and the payload embedded, a detection record what a detector read, and a verifier refuses a + detection that disagrees with the claim or finds no mark in a marked object, and, under a + policy that asks for both layers, a synthesized object with no marking. No watermark scheme is + registered: the scheme is a slot a later revision fills. +- **AI participants in rooms.** live-rooms §4.4's grant attribute `ai` names the model that acts + for a participant; its chat, reactions and captions state origin 2 or 3 with that model, and + the room verifier refuses one that does not (`ai_unmarked`, live-rooms §9.2 step 6). Chat and + reaction events of live-events gain an optional `origin` and `model` after their last field. + Every track an AI participant publishes needs a synthesized track claim naming its model. +- **Certified synthesis.** A review (`wai-review`) is a signed verdict over one exact + reconstruction at `decode-equivalence` or `generation-equivalence`. A player presents it only + where its latest presentation of the object holds that tier on its decode path and output the + reconstruction reviewed, so one review holds for every viewer of exact output. +- **Labels.** A label policy (`wai-label-policy`) says which classes are labelled, with which + visual and audible assets, at what interval and grace; a player's signed log (`wai-player`) of + presentations, labels shown, resumptions after a break and reviews presented is held to it: + a label at first exposure, after each interruption and within the interval. The EU icons the + Code of Practice's Annex 1 publishes are pinned by SHA-256; no audible EU label is published. +- **Reference and corpus.** `wai-rs` feature `synthesis` (`src/synthesis.rs`). + `live-conformance/synthesis/`, written from fixed seeds by `wai_synthesis_vectors`: a + capture's chain through a neural codec and both pinned derivations, a float super-resolution + asserted, generated media in both layers, a room with an AI host, a certified and labelled + session, and one refused case per reason. Checked by `tests/synthesis.rs` and by + `live-conformance/synthesis_verify.py`, a second implementation that shares no code with + `wai-rs`, verifies each room with `live_rooms_verify.py`, runs the derivations and writes each + statement itself, and requires every mutated copy of the corpus to fail. The registry lists + the record and its document kinds. + ### `wai.edge.derivation`: an edge that derives, under its own receipts, chained to its source - **The extension** ([deriving-edge](extensions/deriving-edge.md)) registers the record diff --git a/wai/REGISTRY.md b/wai/REGISTRY.md index 200e5bbfb..5744ccdca 100644 --- a/wai/REGISTRY.md +++ b/wai/REGISTRY.md @@ -43,8 +43,8 @@ Every registered string has one kind. `wai.live.room`, `wai.capture.take`, `wai.live.compose`, `wai.live.recording`, `wai.live.metering`, `wai.live.steering`, `wai.live.moderation`, `wai.live.e2ee`, `wai.live.watermark`, `wai.live.ads` and `wai.live.provenance`; timed text, - `wai.text.timed`; the deriving edge's record, `wai.edge.derivation`). It names no payload - format. + `wai.text.timed`; the deriving edge's record, `wai.edge.derivation`; the origin claims of + media, `wai.media.synthesis`). It names no payload format. A derivation or record string is not a payload capability, so it is not dispatchable (SPEC §4): dispatch never takes an envelope's capability that names @@ -209,6 +209,7 @@ and a role are fixed once registered. | `wai.live.room` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | | `wai.live.steering` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | | `wai.live.watermark` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | +| `wai.media.synthesis` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | | `wai.mesh.draco` | payload | registered | volumetric | standalone | royalty-free | standard-unclassified | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | | `wai.mesh.glb` | payload | registered | volumetric | standalone | royalty-free | standard-unclassified | — | §5 Interactive, temporal & computed content | | `wai.meta.dovi` | companion | registered | metadata | companion | proprietary | standard-unclassified | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | @@ -361,11 +362,17 @@ and a role are fixed once registered. | `wai` | catalog | defined | moq-streaming-format §3 | | `wai-c2pa` | catalog | defined | live-provenance §10.2 | | `wai-derivation` | document-kind | defined | deriving-edge §5.1 | +| `wai-detection` | document-kind | defined | synthesis §7.3 | | `wai-key-status` | document-kind | defined | jwp-receipts §7.9 | +| `wai-label-policy` | document-kind | defined | synthesis §10.1 | +| `wai-marking` | document-kind | defined | synthesis §7.2 | | `wai-module-grant` | document-kind | defined | jwp-receipts §7.9 | | `wai-module-grants` | document-kind | defined | http-delivery §8.4 | +| `wai-origin` | document-kind | defined | synthesis §3.1 | +| `wai-player` | document-kind | defined | synthesis §10.3 | | `wai-receipt-index` | document-kind | defined | http-delivery §8.2 | | `wai-receipts` | catalog | defined | moq-streaming-format §4.1 | +| `wai-review` | document-kind | defined | synthesis §9.1 | | `wai1` | codecs-prefix | defined | http-delivery §4.1 | | `wai1` | fourcc | provisional | http-delivery Appendix B | | `wai2` | codecs-prefix | defined | http-delivery §4.1 | diff --git a/wai/SPEC.md b/wai/SPEC.md index 8e4943d80..e1fb2f1d2 100644 --- a/wai/SPEC.md +++ b/wai/SPEC.md @@ -595,8 +595,8 @@ kind: `wai.live.room`, `wai.capture.take`, `wai.live.compose`, `wai.live.recording`, `wai.live.metering`, `wai.live.steering`, `wai.live.moderation`, `wai.live.e2ee`, `wai.live.watermark`, `wai.live.ads` and `wai.live.provenance`, and timed text, - `wai.text.timed`; and an edge's derivation record, `wai.edge.derivation`), which names no - payload format. + `wai.text.timed`; an edge's derivation record, `wai.edge.derivation`; and the origin claims of + media, `wai.media.synthesis`), which names no payload format. Only payload, companion and refinement capabilities have a media class, a fidelity tier, a replicate class and a role. A derivation or record string is not a payload @@ -1118,6 +1118,29 @@ whose proof is an RFC 9942 receipt of inclusion in the repository's RFC 9162 log boxes of each segment ride a provenance track beside the media track: [`extensions/live-provenance.md`](extensions/live-provenance.md). +And for the **origin** of any media — `wai.media.synthesis`: 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) +or synthesized (generated). It names each input by the link hash of the input's own claim, and +its class is the greatest of its step's and its inputs', so the class rises along every chain +of derivations and never falls; a generator's own envelope (a `GenerativeDeterministic` or +`Behavioral` capability, or `intent` `create`) cannot be claimed as anything else. The claim's +IPTC digital source type and its C2PA statement (the actions, and one `c2pa.ai-disclosure` +assertion per trained model) are functions of it; a codec's step is a conversion, which asserts +no source of its own, as a decode does under energy-binding §6.1. An assistive claim over one +of two pinned integer derivations (a 2× upscale and a 3 × 3 denoise) is proved by recomputing +the output from the input's decoded samples; any other assistive step can only assert it. The +signed claim is the first of two marking layers; the second is a watermark whose payload is the +claim's class and the head of its link hash, and a detection that disagrees with the claim is +refused (the watermark algorithm is a slot a later revision fills). A room's AI participant, +marked in its grant, states its machine origin in its chat, reactions and captions, and labels +every track it publishes. A review signed over one exact reconstruction is presented only by a +player whose decode path holds the review's tier and whose output is that reconstruction, so one +review holds for every viewer of exact output. A label policy says when a label is due (first +exposure, after each interruption, at an interval), with the EU icons pinned by digest, and a +player's signed log shows whether it was shown: +[`extensions/synthesis.md`](extensions/synthesis.md). + ### Payloads of the WAI-native integer codecs This revision pins a payload format to each WAI-native integer codec whose row named diff --git a/wai/extensions/live-events.md b/wai/extensions/live-events.md index 0bb625358..7238ec7b5 100644 --- a/wai/extensions/live-events.md +++ b/wai/extensions/live-events.md @@ -176,7 +176,17 @@ producer **MUST NOT** write a summary its message contradicts. ### 4.4 `chat` -One text field: the message. +| field | size | meaning | +|---|---|---| +| `text` | text | the message | +| `origin` | u8 | optional: 1 human, 2 machine, 3 machine-reviewed | +| `model` | 32 | only when `origin` is 2 or 3: a digest naming the model that produced the message | + +`origin` is present exactly when a byte follows `text`; a chat without it states no origin. +`origin` and `model` mean what a caption's mean (§4.10): they are the message's +machine-generation disclosure, and a sink that labels machine-generated messages takes the +label from them. A room may require them of a principal: [live-rooms](live-rooms.md) §4.4 +marks an AI participant, whose every chat states origin 2 or 3. ### 4.5 `reaction` @@ -186,6 +196,8 @@ One text field: the message. | `value` | u16 | not 0: for emoji and raise, that draft's §4.5 integer encodings; for the others, the application's | | `flags` | u8 | bit 0: a target follows; bit 1: this retracts the reaction it repeats; every other bit 0 | | `target` | 32 | only when bit 0 is set: the digest of the event reacted to | +| `origin` | u8 | optional, as a chat's (§4.4): present exactly when a byte follows the field before it | +| `model` | 32 | only when `origin` is 2 or 3 | A reaction without a target is **standalone**. Standalone reactions are held to the policy's reaction rate (§6.3), as that draft holds them to one per second per device @@ -633,7 +645,9 @@ In a sealed package the events are one component, which forgeries that a lenient Ed25519 verifier accepts, a broken chain, a reorder, a replayed `seq`, `t` going back, an oversize payload, a kind not permitted, both rates exceeded, invalid UTF-8 and overlong text, truncated and malformed records, an unknown version and - kind, the wrong room, malformed payloads and a stated tally the replay does not reach. + kind, the wrong room, malformed payloads (a chat stating a machine origin without its + model among them) and a stated tally the replay does not reach. Chats and a reaction + stating each origin have an accepted vector. - **policy cases**: policy records, read (§5) or refused with their reason. - **binding cases**: tags, boxes and MSF records, read back to their record (§8) or refused with their reason. diff --git a/wai/extensions/live-rooms.md b/wai/extensions/live-rooms.md index af4699bdb..d1b8bd7b2 100644 --- a/wai/extensions/live-rooms.md +++ b/wai/extensions/live-rooms.md @@ -276,6 +276,20 @@ device captures for, as 64 lowercase hexadecimal digits of its key; and `minor` or `false`), whether the authority marks that principal a minor. Attributes are signed with the grant, so a layout computed from them is computed from what the authority stated. +One more attribute marks an **AI participant**: `ai`, the digest naming the model that acts +for the principal (a prior's digest, SPEC §3.1), as 64 lowercase hexadecimal digits. A grant +whose `ai` is not that form breaks this section's rule (§4.3 step 5, `grant_invalid`). The +authority states it, as it states `minor`; a principal is an AI participant from the instant +a grant carrying it is its grant in effect until one without it is. An AI participant's +`chat`, `reaction` and `caption` events state that a machine produced them, with that model +(§9.2 step 6), and the tracks it publishes are synthesized media +([synthesis](synthesis.md) §8). A relay that forwards a room's events **MUST NOT** forward +one that §9.2 step 6 refuses. A sink **MUST** show, with every presence, voice, avatar and +message of an AI participant, that it is an AI system: the information Regulation (EU) +2024/1689 Article 50(1) asks a system that interacts with people to give. The sink's showing +it is an engineering requirement of this extension, and is not a statement about the +Regulation. + ### 4.5 How a receipt names a grant A receipt that names the grant it was carried under does so with the grant reference @@ -598,6 +612,10 @@ record, in the merged order: 5. A `control` event of such a principal needs a power of its grant in effect (§3.3): `record_start` and `record_stop` the record power, any other command the direct power. Otherwise `power_not_granted`. +6. A `chat`, `reaction` or `caption` event of a principal whose grant in effect carries + `ai` (§4.4) states origin 2 (machine) or 3 (machine-reviewed), with the model `ai` + names (live-events §4.4, §4.5, §4.10). One that states no origin, origin 1, or another + model is `ai_unmarked`. Each principal's effective policy at `t` is so the policy of its grant in effect at `t`, and the room keys' is the authority's. Each record is reported with the grant it was @@ -904,6 +922,7 @@ The codes this extension adds, each with where it is raised: | `participant_limit` | §3.5, §7.2 `join` | | `outside_grant_window` | §4.3, §7.2, §9.2 step 4 | | `power_not_granted` | §9.2 step 5 | +| `ai_unmarked` | §4.4, §9.2 step 6 | | `publish_not_permitted` | §7.2 `publish`, `grant_exchange`, `takeover` | | `profile_not_granted` | §7.2 `publish`, `grant_exchange`, `takeover` | | `already_published` | §7.2 `publish` | @@ -953,8 +972,11 @@ key-log head or none, and chains, with the outcome §9.4 gives them. - **Gap**: a chain with a record not delivered. - **Refused**: each reason of §11 at least once, each vector a control of one defect, and the codes of live-events §9 that a descriptor, a grant or a record of a room raises; - among them a grant with the seek power whose role lacks it, and a grant and a role with - a power above bit 4. + among them a grant with the seek power whose role lacks it, a grant and a role with + a power above bit 4, a grant whose `ai` attribute does not read, and an AI participant's + chat that states no origin, one that states origin 1 and one that names another model. +- **AI participant**: a host whose grant names the model that acts for it, its chat and + its caption stating that model, beside a person whose chat and reaction state no origin. - **Proofs** (§10.5), in the file's `proofs`: a grant of a room presented with a proof over MOQT (verb `SETUP`) and over HTTP (a WHIP `POST` and a `DELETE`, in their header forms), each accepted; and, each refused with its reason, a grant presented with no proof, a diff --git a/wai/extensions/synthesis.md b/wai/extensions/synthesis.md new file mode 100644 index 000000000..d8eb9792c --- /dev/null +++ b/wai/extensions/synthesis.md @@ -0,0 +1,760 @@ +# 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). diff --git a/wai/live-conformance/README.md b/wai/live-conformance/README.md index 1a2e20d09..721385c8c 100644 --- a/wai/live-conformance/README.md +++ b/wai/live-conformance/README.md @@ -18,6 +18,7 @@ and the independent verifier of each, beside it. | [`e2ee/`](e2ee/) | [confidentiality](../extensions/confidentiality.md) (`wai.live.e2ee`) | `e2ee_verify.py` | | [`watermark/`](watermark/) | [forensic-watermark](../extensions/forensic-watermark.md) (`wai.live.watermark`) | `live_watermark_verify.py` | | [`c2pa-live/`](c2pa-live/) | [live-provenance](../extensions/live-provenance.md) (`wai.live.provenance`) | `c2pa_live_verify.py` | +| [`synthesis/`](synthesis/) | [synthesis](../extensions/synthesis.md) (`wai.media.synthesis`) | `synthesis_verify.py` | **Provenance.** Generated here: `wai_live_events_vectors gen` writes `events/`, `wai_live_room_vectors gen` writes `rooms/`, `wai_capture_take_vectors gen` writes @@ -26,12 +27,12 @@ writes `recordings/`, `wai_timed_text_vectors gen` writes `captions/` and `wai_live_metering_vectors gen` writes `metering/`, `wai_energy_steering_vectors gen` writes `steering/`, `wai_live_moderation_vectors gen` writes `moderation/`, `wai_live_watermark_vectors gen` writes `watermark/`, `wai_live_ads_vectors gen` writes -`ads/` and `wai_live_provenance_vectors gen` writes `c2pa-live/`, each from records authored -in code, from fixed seeds (`events/README.md`, +`ads/`, `wai_live_provenance_vectors gen` writes `c2pa-live/` and `wai_synthesis_vectors gen` writes +`synthesis/`, each from records authored in code, from fixed seeds (`events/README.md`, `rooms/README.md`, `takes/README.md`, `compose/README.md`, `recordings/README.md`, `captions/README.md`, `metering/README.md`, `steering/README.md`, `moderation/README.md`, -`watermark/README.md`, `ads/README.md`, `c2pa-live/README.md`); `c2pa-live/` cuts its media -from the packaging corpus's AV1 floor. `joule_moq_e2ee_vectors gen` (in +`watermark/README.md`, `ads/README.md`, `c2pa-live/README.md`, `synthesis/README.md`); +`c2pa-live/` cuts its media from the packaging corpus's AV1 floor. `joule_moq_e2ee_vectors gen` (in `joule-code/joule-code-rs/crates/joule-moq-mls`) writes `e2ee/vectors.json` from fixed seeds, and `e2ee/` also holds the test vectors RFC 9605 and `draft-ietf-moq-secure-objects-01` publish, as printed (`e2ee/README.md`). One recorded record: `metering/relay-record.wmh` and @@ -55,4 +56,5 @@ python3 live_ads_verify.py ads python3 e2ee_verify.py e2ee python3 live_watermark_verify.py watermark python3 c2pa_live_verify.py c2pa-live +python3 synthesis_verify.py synthesis ``` diff --git a/wai/live-conformance/events/vectors.json b/wai/live-conformance/events/vectors.json index 10c8fcd9c..80a48e7a2 100644 --- a/wai/live-conformance/events/vectors.json +++ b/wai/live-conformance/events/vectors.json @@ -432,6 +432,52 @@ } ] }, + { + "name": "kind_chat_origin", + "about": "chat and reaction stating their origin (§4.4, §4.5): a machine message with its model's digest, a machine-reviewed one, a human one stating origin 1, and a machine reaction targeting the first", + "room": "main", + "default_policy": "default", + "policies": {}, + "events": [ + "5745563101994ce44cd8c3fa2781c7976335330a5e718019657440cb46a77db3210900ce31c2a1e8c1326f0811f77d5ef3628991d76af48f4a089e7ed08541846e351fce930000000000000000000000000000000000000000000000000000000000000000000000000000000040420f00000000000003480025004865726520697320612073756d6d617279206f66207468652074616c6b20736f206661722e02b16dcccea3b663fa41a0b4c59228cb2dddbbf2510df387ed330dc4a11aeeae1cd1c8363bda75e23afcaba414d396c3dcdeaf82c1f2954261ccb7d20aa5a527d65cf9a59fb50c9992e1afe2b51a87df6093dc255bddd3ca137bdef58caecf160c", + "5745563101994ce44cd8c3fa2781c7976335330a5e718019657440cb46a77db3210900ce31c2a1e8c1326f0811f77d5ef3628991d76af48f4a089e7ed08541846e351fce9301000000000000002ff65886fa1f94c8cd7c18a84d052b6f8e1d32c36f975bbc8f23048fef770f3580841e000000000000034b00280052657669657765643a207468652073756d6d6172792061626f76652069732061636375726174652e03b16dcccea3b663fa41a0b4c59228cb2dddbbf2510df387ed330dc4a11aeeae1c74553836741ceae7553cc599849aa68d35290a1e831a027c82a9a6def82b8f28dd6024eed3368c80a22f37559328e881733901207ba09ea7a2a9a1f55acb5607", + "5745563101994ce44cd8c3fa2781c7976335330a5e718019657440cb46a77db3210900ce31c2a1e8c1326f0811f77d5ef3628991d76af48f4a089e7ed08541846e351fce9302000000000000007bcf54fed4a5769d4c7987599718e6f2359e9f6fb17cf2eb754b6b79f034c116c0c62d00000000000003150012005479706564206279206120706572736f6e2e012df40b7fb2b654619a6d2b19133102bcbdb6b7bb5bc936a9bde2a6924842041a561080468342519243994d6e34b466c8b380ac4da5a5454739460eb48e6a8d06", + "5745563101994ce44cd8c3fa2781c7976335330a5e718019657440cb46a77db3210900ce31c2a1e8c1326f0811f77d5ef3628991d76af48f4a089e7ed08541846e351fce930300000000000000a806a8ec963a5a2bea734fd5fbdaeb68eb8890a7b1ac0e67ed8106decbe90bf800093d000000000000044500010100012ff65886fa1f94c8cd7c18a84d052b6f8e1d32c36f975bbc8f23048fef770f3502b16dcccea3b663fa41a0b4c59228cb2dddbbf2510df387ed330dc4a11aeeae1c06d90f8c5c3615fed522ab121c32ad98b740c874c0ebd1b391f55162d1a0ecec81fa90563807f382142d37ee1868d4107291c9456fa8bdd8630d0ca8f9b69f07" + ], + "outcome": "accept", + "digests": [ + "2ff65886fa1f94c8cd7c18a84d052b6f8e1d32c36f975bbc8f23048fef770f35", + "7bcf54fed4a5769d4c7987599718e6f2359e9f6fb17cf2eb754b6b79f034c116", + "a806a8ec963a5a2bea734fd5fbdaeb68eb8890a7b1ac0e67ed8106decbe90bf8", + "e262cac60f3b23c92d7e3f64aa7af8725c7f8199f5fc3588051b46a32e997a9c" + ], + "tally": { + "state_hash": "771b30268d4b5a93046d9e87dbbc10bae6fc76c4c2d5dde9071f801bfdb9a68d", + "polls": [] + }, + "bindings": [ + { + "msf": "{\"t\":1792108801000,\"data\":{\"wev1\":\"V0VWMQGZTORM2MP6J4HHl2M1MwpecYAZZXRAy0anfbMhCQDOMcKh6MEybwgR931e82KJkddq9I9KCJ5-0IVBhG41H86TAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBCDwAAAAAAAANIACUASGVyZSBpcyBhIHN1bW1hcnkgb2YgdGhlIHRhbGsgc28gZmFyLgKxbczOo7Zj-kGgtMWSKMst3bvyUQ3zh-0zDcShGu6uHNHINjvadeI6_KukFNOWw9zer4LB8pVCYcy30gqlpSfWXPmln7UMmZLhr-K1GoffYJPcJVvd08oTe971jK7PFgw\"}}", + "daterange": "#EXT-X-DATERANGE:ID=\"2ff65886fa1f94c8cd7c18a84d052b6f8e1d32c36f975bbc8f23048fef770f35\",CLASS=\"science.transaction.wai.event\",START-DATE=\"2026-10-16T00:00:01.000Z\",X-WAI-EVENT=\"V0VWMQGZTORM2MP6J4HHl2M1MwpecYAZZXRAy0anfbMhCQDOMcKh6MEybwgR931e82KJkddq9I9KCJ5-0IVBhG41H86TAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBCDwAAAAAAAANIACUASGVyZSBpcyBhIHN1bW1hcnkgb2YgdGhlIHRhbGsgc28gZmFyLgKxbczOo7Zj-kGgtMWSKMst3bvyUQ3zh-0zDcShGu6uHNHINjvadeI6_KukFNOWw9zer4LB8pVCYcy30gqlpSfWXPmln7UMmZLhr-K1GoffYJPcJVvd08oTe971jK7PFgw\"", + "emsg": "00000136656d736701000000000003e80000000000002af8000000002ff6588675726e3a7761693a6576656e743a310063686174005745563101994ce44cd8c3fa2781c7976335330a5e718019657440cb46a77db3210900ce31c2a1e8c1326f0811f77d5ef3628991d76af48f4a089e7ed08541846e351fce930000000000000000000000000000000000000000000000000000000000000000000000000000000040420f00000000000003480025004865726520697320612073756d6d617279206f66207468652074616c6b20736f206661722e02b16dcccea3b663fa41a0b4c59228cb2dddbbf2510df387ed330dc4a11aeeae1cd1c8363bda75e23afcaba414d396c3dcdeaf82c1f2954261ccb7d20aa5a527d65cf9a59fb50c9992e1afe2b51a87df6093dc255bddd3ca137bdef58caecf160c" + }, + { + "msf": "{\"t\":1792108802000,\"data\":{\"wev1\":\"V0VWMQGZTORM2MP6J4HHl2M1MwpecYAZZXRAy0anfbMhCQDOMcKh6MEybwgR931e82KJkddq9I9KCJ5-0IVBhG41H86TAQAAAAAAAAAv9liG-h-UyM18GKhNBStvjh0yw2-XW7yPIwSP73cPNYCEHgAAAAAAAANLACgAUmV2aWV3ZWQ6IHRoZSBzdW1tYXJ5IGFib3ZlIGlzIGFjY3VyYXRlLgOxbczOo7Zj-kGgtMWSKMst3bvyUQ3zh-0zDcShGu6uHHRVODZ0HOrnVTzFmYSapo01KQoegxoCfIKppt74K48o3WAk7tM2jICiLzdVkyjogXM5ASB7oJ6noqmh9VrLVgc\"}}", + "daterange": "#EXT-X-DATERANGE:ID=\"7bcf54fed4a5769d4c7987599718e6f2359e9f6fb17cf2eb754b6b79f034c116\",CLASS=\"science.transaction.wai.event\",START-DATE=\"2026-10-16T00:00:02.000Z\",X-WAI-EVENT=\"V0VWMQGZTORM2MP6J4HHl2M1MwpecYAZZXRAy0anfbMhCQDOMcKh6MEybwgR931e82KJkddq9I9KCJ5-0IVBhG41H86TAQAAAAAAAAAv9liG-h-UyM18GKhNBStvjh0yw2-XW7yPIwSP73cPNYCEHgAAAAAAAANLACgAUmV2aWV3ZWQ6IHRoZSBzdW1tYXJ5IGFib3ZlIGlzIGFjY3VyYXRlLgOxbczOo7Zj-kGgtMWSKMst3bvyUQ3zh-0zDcShGu6uHHRVODZ0HOrnVTzFmYSapo01KQoegxoCfIKppt74K48o3WAk7tM2jICiLzdVkyjogXM5ASB7oJ6noqmh9VrLVgc\"", + "emsg": "00000139656d736701000000000003e80000000000002ee0000000007bcf54fe75726e3a7761693a6576656e743a310063686174005745563101994ce44cd8c3fa2781c7976335330a5e718019657440cb46a77db3210900ce31c2a1e8c1326f0811f77d5ef3628991d76af48f4a089e7ed08541846e351fce9301000000000000002ff65886fa1f94c8cd7c18a84d052b6f8e1d32c36f975bbc8f23048fef770f3580841e000000000000034b00280052657669657765643a207468652073756d6d6172792061626f76652069732061636375726174652e03b16dcccea3b663fa41a0b4c59228cb2dddbbf2510df387ed330dc4a11aeeae1c74553836741ceae7553cc599849aa68d35290a1e831a027c82a9a6def82b8f28dd6024eed3368c80a22f37559328e881733901207ba09ea7a2a9a1f55acb5607" + }, + { + "msf": "{\"t\":1792108803000,\"data\":{\"wev1\":\"V0VWMQGZTORM2MP6J4HHl2M1MwpecYAZZXRAy0anfbMhCQDOMcKh6MEybwgR931e82KJkddq9I9KCJ5-0IVBhG41H86TAgAAAAAAAAB7z1T-1KV2nUx5h1mXGObyNZ6fb7F88ut1S2t58DTBFsDGLQAAAAAAAAMVABIAVHlwZWQgYnkgYSBwZXJzb24uAS30C3-ytlRhmm0rGRMxAry9tre7W8k2qb3ippJIQgQaVhCARoNCUZJDmU1uNLRmyLOArE2lpUVHOUYOtI5qjQY\"}}", + "daterange": "#EXT-X-DATERANGE:ID=\"a806a8ec963a5a2bea734fd5fbdaeb68eb8890a7b1ac0e67ed8106decbe90bf8\",CLASS=\"science.transaction.wai.event\",START-DATE=\"2026-10-16T00:00:03.000Z\",X-WAI-EVENT=\"V0VWMQGZTORM2MP6J4HHl2M1MwpecYAZZXRAy0anfbMhCQDOMcKh6MEybwgR931e82KJkddq9I9KCJ5-0IVBhG41H86TAgAAAAAAAAB7z1T-1KV2nUx5h1mXGObyNZ6fb7F88ut1S2t58DTBFsDGLQAAAAAAAAMVABIAVHlwZWQgYnkgYSBwZXJzb24uAS30C3-ytlRhmm0rGRMxAry9tre7W8k2qb3ippJIQgQaVhCARoNCUZJDmU1uNLRmyLOArE2lpUVHOUYOtI5qjQY\"", + "emsg": "00000103656d736701000000000003e800000000000032c800000000a806a8ec75726e3a7761693a6576656e743a310063686174005745563101994ce44cd8c3fa2781c7976335330a5e718019657440cb46a77db3210900ce31c2a1e8c1326f0811f77d5ef3628991d76af48f4a089e7ed08541846e351fce9302000000000000007bcf54fed4a5769d4c7987599718e6f2359e9f6fb17cf2eb754b6b79f034c116c0c62d00000000000003150012005479706564206279206120706572736f6e2e012df40b7fb2b654619a6d2b19133102bcbdb6b7bb5bc936a9bde2a6924842041a561080468342519243994d6e34b466c8b380ac4da5a5454739460eb48e6a8d06" + }, + { + "msf": "{\"t\":1792108804000,\"data\":{\"wev1\":\"V0VWMQGZTORM2MP6J4HHl2M1MwpecYAZZXRAy0anfbMhCQDOMcKh6MEybwgR931e82KJkddq9I9KCJ5-0IVBhG41H86TAwAAAAAAAACoBqjsljpaK-pzT9X72uto64iQp7GsDmftgQbey-kL-AAJPQAAAAAAAARFAAEBAAEv9liG-h-UyM18GKhNBStvjh0yw2-XW7yPIwSP73cPNQKxbczOo7Zj-kGgtMWSKMst3bvyUQ3zh-0zDcShGu6uHAbZD4xcNhX-1SKrEhwyrZi3QMh0wOvRs5H1UWLRoOzsgfqQVjgH84IULTfuGGjUEHKRyUVvqL3YYw0MqPm2nwc\"}}", + "daterange": "#EXT-X-DATERANGE:ID=\"e262cac60f3b23c92d7e3f64aa7af8725c7f8199f5fc3588051b46a32e997a9c\",CLASS=\"science.transaction.wai.event\",START-DATE=\"2026-10-16T00:00:04.000Z\",X-WAI-EVENT=\"V0VWMQGZTORM2MP6J4HHl2M1MwpecYAZZXRAy0anfbMhCQDOMcKh6MEybwgR931e82KJkddq9I9KCJ5-0IVBhG41H86TAwAAAAAAAACoBqjsljpaK-pzT9X72uto64iQp7GsDmftgQbey-kL-AAJPQAAAAAAAARFAAEBAAEv9liG-h-UyM18GKhNBStvjh0yw2-XW7yPIwSP73cPNQKxbczOo7Zj-kGgtMWSKMst3bvyUQ3zh-0zDcShGu6uHAbZD4xcNhX-1SKrEhwyrZi3QMh0wOvRs5H1UWLRoOzsgfqQVjgH84IULTfuGGjUEHKRyUVvqL3YYw0MqPm2nwc\"", + "emsg": "00000137656d736701000000000003e800000000000036b000000000e262cac675726e3a7761693a6576656e743a31007265616374696f6e005745563101994ce44cd8c3fa2781c7976335330a5e718019657440cb46a77db3210900ce31c2a1e8c1326f0811f77d5ef3628991d76af48f4a089e7ed08541846e351fce930300000000000000a806a8ec963a5a2bea734fd5fbdaeb68eb8890a7b1ac0e67ed8106decbe90bf800093d000000000000044500010100012ff65886fa1f94c8cd7c18a84d052b6f8e1d32c36f975bbc8f23048fef770f3502b16dcccea3b663fa41a0b4c59228cb2dddbbf2510df387ed330dc4a11aeeae1c06d90f8c5c3615fed522ab121c32ad98b740c874c0ebd1b391f55162d1a0ecec81fa90563807f382142d37ee1868d4107291c9456fa8bdd8630d0ca8f9b69f07" + } + ] + }, { "name": "kind_caption", "about": "caption (§4.10): human, machine and machine-reviewed origins, the latter two with a model digest, three language tags, and a caption with no stated duration", @@ -1420,6 +1466,19 @@ "reason": "malformed_payload", "index": 0 }, + { + "name": "malformed_payload_origin", + "about": "a chat stating origin 2 with no model digest after it (§4.4)", + "room": "main", + "default_policy": "default", + "policies": {}, + "events": [ + "5745563101994ce44cd8c3fa2781c7976335330a5e718019657440cb46a77db3210900ce31c2a1e8c1326f0811f77d5ef3628991d76af48f4a089e7ed08541846e351fce930000000000000000000000000000000000000000000000000000000000000000000000000000000040420f000000000000030f000c007768696368206d6f64656c3f02b8bbfca6c533438f2f05fdd56a1354fe50609951f58fef3cd0087f8bb25afe82d87dc1114046480dc6a6a18df663a91b25172f1c6c356084519d478b6107f207" + ], + "outcome": "refuse", + "reason": "malformed_payload", + "index": 0 + }, { "name": "malformed_payload_poll", "about": "a poll_open with one option (§4.6)", diff --git a/wai/live-conformance/live_events_verify.py b/wai/live-conformance/live_events_verify.py index f681fe30a..ff38b50fb 100644 --- a/wai/live-conformance/live_events_verify.py +++ b/wai/live-conformance/live_events_verify.py @@ -164,6 +164,19 @@ def splice_info_section_ok(b): return len(b) >= 3 and b[0] == 0xFC and 3 + (((b[1] & 0x0F) << 8) | b[2]) == len(b) +def stated_origin(r): + """§4.4, §4.5: the origin a chat or a reaction may state after its last field, as + (origin, model), or (None, None) when no byte follows.""" + if r.done(): + return None, None + o = r.u8() + if o == 1: + return 1, None + if o in (2, 3): + return o, r.take(32) + raise Refuse("malformed_payload") + + def read_payload(kind, b, max_cp): """§4: the payload as a dict, or Refuse.""" m = "malformed_payload" @@ -188,6 +201,7 @@ def read_payload(kind, b, max_cp): raise Refuse(m) elif kind == 0x03: p["text"] = text(r, max_cp) + p["origin"], p["model"] = stated_origin(r) elif kind == 0x04: p["type"] = r.u8() if p["type"] not in (1, 2, 3, 4, 5): @@ -200,6 +214,7 @@ def read_payload(kind, b, max_cp): raise Refuse(m) p["target"] = r.take(32) if flags & 1 else None p["remove"] = bool(flags & 2) + p["origin"], p["model"] = stated_origin(r) elif kind == 0x05: p["question"] = text(r, max_cp) n = r.u8() diff --git a/wai/live-conformance/live_rooms_verify.py b/wai/live-conformance/live_rooms_verify.py index 1a3834268..63ee99a0f 100644 --- a/wai/live-conformance/live_rooms_verify.py +++ b/wai/live-conformance/live_rooms_verify.py @@ -73,7 +73,7 @@ REASONS = [ "grant_exceeds_room", "exchange_unlinked", "exchange_mismatch", "lifecycle_malformed", "grant_unknown", "grant_wrong_principal", "grant_revoked", "grant_reused", "already_present", "not_present", "participant_limit", "outside_grant_window", - "power_not_granted", "publish_not_permitted", "profile_not_granted", "already_published", + "power_not_granted", "ai_unmarked", "publish_not_permitted", "profile_not_granted", "already_published", "not_published", "slot_unknown", "slot_full", "publisher_limit", "exchange_not_current", "exchange_off_tick", "takeover_not_higher", "failover_not_standby", "failover_early", "pop_missing", "pop_malformed", "pop_wrong_grant", "pop_wrong_key", "pop_bad_signature", @@ -395,11 +395,22 @@ def grant_fields_ok(g, d, held): media.add(m) if len(g["origins"]) > 8 or not all(is_token(o, 255) for o in g["origins"]): return False - if read_attrs(g["attrs"]) is None: + attrs = read_attrs(g["attrs"]) + if attrs is None: + return False + if any(k == "ai" and not re.fullmatch(r"[0-9a-f]{64}", v) for k, v in attrs): return False return all(h["digest"] != g["digest"] for h in held) +def ai_model(g): + """§4.4: the model an AI participant's grant names, as bytes, or None.""" + for k, v in read_attrs(g["attrs"]) or []: + if k == "ai": + return bytes.fromhex(v) + return None + + def publishes(g, m): return m in MEDIA and g["publish"] >> (m - 1) & 1 == 1 @@ -1035,6 +1046,10 @@ def step(d, room, ver, seen, auth, b): need = RECORD if payload["command"] in (1, 2) else DIRECT if g["powers"] & need == 0: raise Refuse("power_not_granted") + model = ai_model(g) + if model is not None and payload["kind"] in (0x03, 0x04, 0x0A): + if payload["origin"] not in (2, 3) or payload["model"] != model: + raise Refuse("ai_unmarked") return g["digest"] @@ -1124,7 +1139,7 @@ def main(corpus_dir): missing = [r for r in REASONS if r not in reasons_seen] if missing: fails.append("no vector refused with %s" % ", ".join(missing)) - rooms = ["lesson", "broadcast", "takeover", "redundant_failover", "promote_viewer", "dvr"] + rooms = ["lesson", "broadcast", "takeover", "redundant_failover", "promote_viewer", "dvr", "ai_participant"] absent = [r for r in rooms if outcomes.get(r) != "accept"] if absent: fails.append("rooms not accepted: %s" % ", ".join(absent)) diff --git a/wai/live-conformance/rooms/vectors.json b/wai/live-conformance/rooms/vectors.json index 5c22690ff..3b543d184 100644 --- a/wai/live-conformance/rooms/vectors.json +++ b/wai/live-conformance/rooms/vectors.json @@ -3529,6 +3529,23 @@ 1 ] }, + { + "name": "grant_ai_malformed", + "about": "a grant whose ai attribute is not 64 lowercase hexadecimal digits (§4.4)", + "descriptor": "5752443101988ec20b60d2d23c4c475c1f4e623f115ffbe7659bd0c81c0af9233c28589285c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b00406cd8e95d060040420f0000000000150068747470733a2f2f726f6f6d732e6578616d706c650800000003003c0000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f00000000000504686f737407010102ae06000000000000000000000000000000000000000000000000000000000000096d6f64657261746f720001000108080000000000000000000000000000000000000000000000000000000000000861756469656e636500010100580000000000000000000000000000000000000000000000000000000000000005677565737403010100580100000000000000000000000000000000000000000000000000000000000007656e636f64657203000000000000000000000000000000000000000000000000000000000000000000000002057374616765020100000000046d61696e0202d007000002067370656563680101046f707573000001000000000000100000004000000080bb0000010f0a63616d6572612d37323002020d617630312e302e30354d2e30380b617663312e363430303146010002800268011e0096000000f40100000005d0021e00f4010000dc0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086c97e1a4b2609cb3438fd6c165e0bdc6f016bae7d16c4d37a387f4be3891b46f97c63fbf4814de11aadb3ee409d246a182017b43cacb6a6c64905bceb31c30b", + "grants": [ + "575247310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a2036872df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa100000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a0000000000070101020000000000000000000000000000000000000000000000000000000000000000005745503101ae060000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c65000008067f81349126761a6b7fe0cf3bd66d94800e6a65dc2f9d1d07de1a362f6d95bd8122dad96eed33117981aa9923f907d8a1a236fcf8c14579e586cbe500470b", + "575247310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310158000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c6545000261694000414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141416e3520d47ed02cd4fa26c68cb2a874282c9d370a49429fd985bb64ecbca67ceaf7563a898af9d9091047cd32ad068802707bec6f1820c82b6f0ec86d65ca820b" + ], + "key_log_head": "{\"grant_max_s\":86400,\"issued_at\":\"2026-10-15T00:00:00Z\",\"keys\":[{\"compromised_since\":null,\"id\":\"room-authority\",\"key\":\"988ec20b60d2d23c4c475c1f4e623f115ffbe7659bd0c81c0af9233c28589285\",\"may_sign\":[\"live/room\"],\"status\":\"active\",\"until\":null}],\"kind\":\"wai-key-set\",\"next_root\":\"3328656f3df5d9617e108d46080e23610712c9979d4437e706240f53d6cadf7b\",\"origin\":\"https://rooms.example\",\"prev\":null,\"revoked_modules\":[],\"root\":\"687972706312d9675734bf13b4bcf695ecb1b374de7809242aa91f130c6a1c0c\",\"seq\":0,\"sig\":\"ba55b157ebe88fc97f2b5a5e3556415563552ed88b9931060b9e99ae0a7decefdc63e2cb916b9a5d03c80a0c1823de14e7d8d3fe0b726c2464d823611780290a\",\"status_max_age_s\":3600}", + "chains": [], + "outcome": "refuse", + "reason": "grant_invalid", + "at": [ + "grant", + 1 + ] + }, { "name": "grant_attrs_too_long", "about": "a grant whose attributes take more than 1024 bytes (§4.4)", @@ -4489,6 +4506,471 @@ 1 ] }, + { + "name": "ai_participant", + "about": "a host whose grant's ai attribute names a model: its chat states origin 2 and its caption origin 3, each with that model; a person's chat and reaction state none (§4.4, §9.2 step 6)", + "descriptor": "5752443101988ec20b60d2d23c4c475c1f4e623f115ffbe7659bd0c81c0af9233c28589285c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b00406cd8e95d060040420f0000000000150068747470733a2f2f726f6f6d732e6578616d706c650800000003003c0000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f00000000000504686f737407010102ae06000000000000000000000000000000000000000000000000000000000000096d6f64657261746f720001000108080000000000000000000000000000000000000000000000000000000000000861756469656e636500010100580000000000000000000000000000000000000000000000000000000000000005677565737403010100580100000000000000000000000000000000000000000000000000000000000007656e636f64657203000000000000000000000000000000000000000000000000000000000000000000000002057374616765020100000000046d61696e0202d007000002067370656563680101046f707573000001000000000000100000004000000080bb0000010f0a63616d6572612d37323002020d617630312e302e30354d2e30380b617663312e363430303146010002800268011e0096000000f40100000005d0021e00f4010000dc0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086c97e1a4b2609cb3438fd6c165e0bdc6f016bae7d16c4d37a387f4be3891b46f97c63fbf4814de11aadb3ee409d246a182017b43cacb6a6c64905bceb31c30b", + "grants": [ + "575247310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a2036872df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa100000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a0000000000070101020000000000000000000000000000000000000000000000000000000000000000005745503101ae060000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c6545000261694000383763386330653335666337666136613964346231393232336634303238326663333565396665633265353933383061633562336466616561653664353930385c245e4d368eea2fa46a37c069226e63a3489e51338486b574f405737169de74e0e230e5f27b523eb35cdc5e5a63fe4f104c48556ba46d8433b3db9a265eaa01", + "575247310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310158000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c650000da52cb8a9a33184a5da85c37affe7d9dfc6f4e7a3019ecc07c090358ded448066ac88859718562bb805f34d7da43880addb6098e654e8e51481afb9ee22ee40e" + ], + "key_log_head": "{\"grant_max_s\":86400,\"issued_at\":\"2026-10-15T00:00:00Z\",\"keys\":[{\"compromised_since\":null,\"id\":\"room-authority\",\"key\":\"988ec20b60d2d23c4c475c1f4e623f115ffbe7659bd0c81c0af9233c28589285\",\"may_sign\":[\"live/room\"],\"status\":\"active\",\"until\":null}],\"kind\":\"wai-key-set\",\"next_root\":\"3328656f3df5d9617e108d46080e23610712c9979d4437e706240f53d6cadf7b\",\"origin\":\"https://rooms.example\",\"prev\":null,\"revoked_modules\":[],\"root\":\"687972706312d9675734bf13b4bcf695ecb1b374de7809242aa91f130c6a1c0c\",\"seq\":0,\"sig\":\"ba55b157ebe88fc97f2b5a5e3556415563552ed88b9931060b9e99ae0a7decefdc63e2cb916b9a5d03c80a0c1823de14e7d8d3fe0b726c2464d823611780290a\",\"status_max_age_s\":3600}", + "chains": [ + [ + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001040002df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa13427f23d6605d4ed9be070c149aa749edd535e46fb4a813e47bdd750d546f9654022ff7403111b077255cc79fffeb37146f44c32b5f5f811ee202b6a1811fc80d874544e4e887d493655b3d5f1e26a16664ec23594e8e685b4201ff3d8c9e105", + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b010000000000000066e0e16b1f2817739e3b6f2c9c87086b31f86249123017fc159142eca323af28000000000000000000104000fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c37d406a857ee63ae9fecd4f293211c3e92c6847beb4871066ecfbca21f4a38607dad484d1fc66d48a45d08c1cec17d18889212fe90f204ee16684d701e5ebf4cb837bdd6dd0af53318a5465ac6f44a98f3769e7481e99169975af532a5614809" + ], + [ + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c0000000000000000000000000000000000000000000000000000000000000000000000000000000040420f0000000000000312001000497320616e796f6e652074686572653f882e98ec64ee02bb24da475a9dc9c491bb077b9fbd4cd32c90c73a253d1848e72c394c8a3fe09a0c620fcb5ad17a38d15b96620fd504be6c7dd57f25a889e20a", + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c010000000000000021509022cbf78272fd37df1a2ac940fddcacf6b7c394169b54e256abac734c8200093d000000000000040400040100001557491f18f4601eba695fd96f943ad21fe5e87575fe87a3824c41e81a2e99fd47231c68b03c133f8dd6a33001fe1fa77671adaf576efa3dad5ae35488ac6508" + ], + [ + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a2036872df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa10000000000000000000000000000000000000000000000000000000000000000000000000000000080841e000000000000034300200057656c636f6d652e204920616d20616e206175746f6d6174656420686f73742e0287c8c0e35fc7fa6a9d4b19223f40282fc35e9fec2e59380ac5b3dfaeae6d590818cbffb02f3b3c5c74d381e071119923078b88c258f5dc2ed86b012166e80546d4a2111fe5e0e26a4e24428b73194386e0b4f1f731c50e47737760846cb1ea0f", + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a2036872df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa1010000000000000081cef7e8b20d0491664ba49ee6fbd5f26e16ec4decbcf820a3f7dedb10ac0403c0c62d0000000000000a3200d00700000387c8c0e35fc7fa6a9d4b19223f40282fc35e9fec2e59380ac5b3dfaeae6d590802656e080057656c636f6d652ebeb6999762bad005e0998b47d5b193f5e32326601e9524604b5b39d2eb25fb631cd22f64c01ab4db57ccd4a104e810387806e200748bdce6cdeb0720a7c3280f" + ] + ], + "outcome": "accept", + "room": "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687", + "grant_digests": [ + "3427f23d6605d4ed9be070c149aa749edd535e46fb4a813e47bdd750d546f965", + "37d406a857ee63ae9fecd4f293211c3e92c6847beb4871066ecfbca21f4a3860" + ], + "order": [ + [ + 0, + 0, + null + ], + [ + 0, + 1, + null + ], + [ + 1, + 0, + "37d406a857ee63ae9fecd4f293211c3e92c6847beb4871066ecfbca21f4a3860" + ], + [ + 2, + 0, + "3427f23d6605d4ed9be070c149aa749edd535e46fb4a813e47bdd750d546f965" + ], + [ + 2, + 1, + "3427f23d6605d4ed9be070c149aa749edd535e46fb4a813e47bdd750d546f965" + ], + [ + 1, + 1, + "37d406a857ee63ae9fecd4f293211c3e92c6847beb4871066ecfbca21f4a3860" + ] + ], + "state": { + "present": [ + { + "principal": "2df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa1", + "grant": "3427f23d6605d4ed9be070c149aa749edd535e46fb4a813e47bdd750d546f965" + }, + { + "principal": "fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c", + "grant": "37d406a857ee63ae9fecd4f293211c3e92c6847beb4871066ecfbca21f4a3860" + } + ], + "published": [], + "slots": [ + { + "slot": "stage", + "media": "video", + "live": null, + "standby": null, + "precedence": null + }, + { + "slot": "main", + "media": "video", + "live": null, + "standby": null, + "precedence": null + } + ], + "tracks": [], + "timeline": [ + { + "principal": "2df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa1", + "grant": "3427f23d6605d4ed9be070c149aa749edd535e46fb4a813e47bdd750d546f965", + "t": 0 + }, + { + "principal": "fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c", + "grant": "37d406a857ee63ae9fecd4f293211c3e92c6847beb4871066ecfbca21f4a3860", + "t": 0 + } + ] + }, + "tally": { + "state_hash": "771b30268d4b5a93046d9e87dbbc10bae6fc76c4c2d5dde9071f801bfdb9a68d", + "polls": [] + }, + "moqt": [ + { + "grant": "3427f23d6605d4ed9be070c149aa749edd535e46fb4a813e47bdd750d546f965", + "reval": 60, + "seek": false, + "scopes": [ + [ + [ + 0 + ] + ], + [ + [ + 2, + 6 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687", + "2df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa1", + "audio", + null + ] + ], + [ + [ + 2, + 6 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687", + "2df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa1", + "video", + null + ] + ], + [ + [ + 6 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687", + "_stage", + "video", + null + ] + ], + [ + [ + 6 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687", + "_stage", + null + ], + "catalog" + ], + [ + [ + 6 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687", + "_main", + "video", + null + ] + ], + [ + [ + 6 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687", + "_main", + null + ], + "catalog" + ], + [ + [ + 2, + 6 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687", + "2df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa1", + "screen", + null + ] + ], + [ + [ + 2, + 6 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687", + "2df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa1", + null + ], + "catalog" + ], + [ + [ + 2, + 6 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687", + "2df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa1", + "events", + null + ], + "0" + ], + [ + [ + 3 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687" + ] + ], + [ + [ + 4, + 7, + 8 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687" + ], + "catalog" + ], + [ + [ + 4, + 7, + 8 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687" + ], + "0" + ], + [ + [ + 4, + 7, + 8 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687" + ], + "0_receipts" + ], + [ + [ + 4, + 7, + 8 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687" + ], + "1" + ], + [ + [ + 4, + 7, + 8 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687" + ], + "1_receipts" + ] + ] + }, + { + "grant": "37d406a857ee63ae9fecd4f293211c3e92c6847beb4871066ecfbca21f4a3860", + "reval": 60, + "seek": false, + "scopes": [ + [ + [ + 0 + ] + ], + [ + [ + 2, + 6 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687", + "fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c", + "events", + null + ], + "0" + ], + [ + [ + 3 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687" + ] + ], + [ + [ + 4, + 7, + 8 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687" + ], + "catalog" + ], + [ + [ + 4, + 7, + 8 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687" + ], + "0" + ], + [ + [ + 4, + 7, + 8 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687" + ], + "0_receipts" + ], + [ + [ + 4, + 7, + 8 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687" + ], + "1" + ], + [ + [ + 4, + 7, + 8 + ], + [ + "91ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687" + ], + "1_receipts" + ] + ] + } + ] + }, + { + "name": "ai_unmarked", + "about": "an AI participant's chat that states no origin (§9.2 step 6)", + "descriptor": "5752443101988ec20b60d2d23c4c475c1f4e623f115ffbe7659bd0c81c0af9233c28589285c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b00406cd8e95d060040420f0000000000150068747470733a2f2f726f6f6d732e6578616d706c650800000003003c0000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f00000000000504686f737407010102ae06000000000000000000000000000000000000000000000000000000000000096d6f64657261746f720001000108080000000000000000000000000000000000000000000000000000000000000861756469656e636500010100580000000000000000000000000000000000000000000000000000000000000005677565737403010100580100000000000000000000000000000000000000000000000000000000000007656e636f64657203000000000000000000000000000000000000000000000000000000000000000000000002057374616765020100000000046d61696e0202d007000002067370656563680101046f707573000001000000000000100000004000000080bb0000010f0a63616d6572612d37323002020d617630312e302e30354d2e30380b617663312e363430303146010002800268011e0096000000f40100000005d0021e00f4010000dc0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086c97e1a4b2609cb3438fd6c165e0bdc6f016bae7d16c4d37a387f4be3891b46f97c63fbf4814de11aadb3ee409d246a182017b43cacb6a6c64905bceb31c30b", + "grants": [ + "575247310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a2036872df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa100000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a0000000000070101020000000000000000000000000000000000000000000000000000000000000000005745503101ae060000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c6545000261694000383763386330653335666337666136613964346231393232336634303238326663333565396665633265353933383061633562336466616561653664353930385c245e4d368eea2fa46a37c069226e63a3489e51338486b574f405737169de74e0e230e5f27b523eb35cdc5e5a63fe4f104c48556ba46d8433b3db9a265eaa01", + "575247310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310158000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c650000da52cb8a9a33184a5da85c37affe7d9dfc6f4e7a3019ecc07c090358ded448066ac88859718562bb805f34d7da43880addb6098e654e8e51481afb9ee22ee40e" + ], + "key_log_head": "{\"grant_max_s\":86400,\"issued_at\":\"2026-10-15T00:00:00Z\",\"keys\":[{\"compromised_since\":null,\"id\":\"room-authority\",\"key\":\"988ec20b60d2d23c4c475c1f4e623f115ffbe7659bd0c81c0af9233c28589285\",\"may_sign\":[\"live/room\"],\"status\":\"active\",\"until\":null}],\"kind\":\"wai-key-set\",\"next_root\":\"3328656f3df5d9617e108d46080e23610712c9979d4437e706240f53d6cadf7b\",\"origin\":\"https://rooms.example\",\"prev\":null,\"revoked_modules\":[],\"root\":\"687972706312d9675734bf13b4bcf695ecb1b374de7809242aa91f130c6a1c0c\",\"seq\":0,\"sig\":\"ba55b157ebe88fc97f2b5a5e3556415563552ed88b9931060b9e99ae0a7decefdc63e2cb916b9a5d03c80a0c1823de14e7d8d3fe0b726c2464d823611780290a\",\"status_max_age_s\":3600}", + "chains": [ + [ + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001040002df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa13427f23d6605d4ed9be070c149aa749edd535e46fb4a813e47bdd750d546f9654022ff7403111b077255cc79fffeb37146f44c32b5f5f811ee202b6a1811fc80d874544e4e887d493655b3d5f1e26a16664ec23594e8e685b4201ff3d8c9e105", + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b010000000000000066e0e16b1f2817739e3b6f2c9c87086b31f86249123017fc159142eca323af28000000000000000000104000fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c37d406a857ee63ae9fecd4f293211c3e92c6847beb4871066ecfbca21f4a38607dad484d1fc66d48a45d08c1cec17d18889212fe90f204ee16684d701e5ebf4cb837bdd6dd0af53318a5465ac6f44a98f3769e7481e99169975af532a5614809" + ], + [ + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c0000000000000000000000000000000000000000000000000000000000000000000000000000000040420f0000000000000312001000497320616e796f6e652074686572653f882e98ec64ee02bb24da475a9dc9c491bb077b9fbd4cd32c90c73a253d1848e72c394c8a3fe09a0c620fcb5ad17a38d15b96620fd504be6c7dd57f25a889e20a" + ], + [ + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a2036872df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa10000000000000000000000000000000000000000000000000000000000000000000000000000000080841e000000000000032200200057656c636f6d652e204920616d20616e206175746f6d6174656420686f73742ed3025193682bf2a1635fed1f8919f6b62399702536a5bbb76d1658c9f00056b7d858882840895670a8c9ea39ecd2b96d2845d5e37481852523eec2e6a1bc4b0b" + ] + ], + "outcome": "refuse", + "reason": "ai_unmarked", + "at": [ + "record", + 2, + 0 + ] + }, + { + "name": "ai_unmarked_human", + "about": "an AI participant's chat that states origin 1, human (§9.2 step 6)", + "descriptor": "5752443101988ec20b60d2d23c4c475c1f4e623f115ffbe7659bd0c81c0af9233c28589285c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b00406cd8e95d060040420f0000000000150068747470733a2f2f726f6f6d732e6578616d706c650800000003003c0000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f00000000000504686f737407010102ae06000000000000000000000000000000000000000000000000000000000000096d6f64657261746f720001000108080000000000000000000000000000000000000000000000000000000000000861756469656e636500010100580000000000000000000000000000000000000000000000000000000000000005677565737403010100580100000000000000000000000000000000000000000000000000000000000007656e636f64657203000000000000000000000000000000000000000000000000000000000000000000000002057374616765020100000000046d61696e0202d007000002067370656563680101046f707573000001000000000000100000004000000080bb0000010f0a63616d6572612d37323002020d617630312e302e30354d2e30380b617663312e363430303146010002800268011e0096000000f40100000005d0021e00f4010000dc0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086c97e1a4b2609cb3438fd6c165e0bdc6f016bae7d16c4d37a387f4be3891b46f97c63fbf4814de11aadb3ee409d246a182017b43cacb6a6c64905bceb31c30b", + "grants": [ + "575247310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a2036872df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa100000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a0000000000070101020000000000000000000000000000000000000000000000000000000000000000005745503101ae060000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c6545000261694000383763386330653335666337666136613964346231393232336634303238326663333565396665633265353933383061633562336466616561653664353930385c245e4d368eea2fa46a37c069226e63a3489e51338486b574f405737169de74e0e230e5f27b523eb35cdc5e5a63fe4f104c48556ba46d8433b3db9a265eaa01", + "575247310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310158000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c650000da52cb8a9a33184a5da85c37affe7d9dfc6f4e7a3019ecc07c090358ded448066ac88859718562bb805f34d7da43880addb6098e654e8e51481afb9ee22ee40e" + ], + "key_log_head": "{\"grant_max_s\":86400,\"issued_at\":\"2026-10-15T00:00:00Z\",\"keys\":[{\"compromised_since\":null,\"id\":\"room-authority\",\"key\":\"988ec20b60d2d23c4c475c1f4e623f115ffbe7659bd0c81c0af9233c28589285\",\"may_sign\":[\"live/room\"],\"status\":\"active\",\"until\":null}],\"kind\":\"wai-key-set\",\"next_root\":\"3328656f3df5d9617e108d46080e23610712c9979d4437e706240f53d6cadf7b\",\"origin\":\"https://rooms.example\",\"prev\":null,\"revoked_modules\":[],\"root\":\"687972706312d9675734bf13b4bcf695ecb1b374de7809242aa91f130c6a1c0c\",\"seq\":0,\"sig\":\"ba55b157ebe88fc97f2b5a5e3556415563552ed88b9931060b9e99ae0a7decefdc63e2cb916b9a5d03c80a0c1823de14e7d8d3fe0b726c2464d823611780290a\",\"status_max_age_s\":3600}", + "chains": [ + [ + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001040002df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa13427f23d6605d4ed9be070c149aa749edd535e46fb4a813e47bdd750d546f9654022ff7403111b077255cc79fffeb37146f44c32b5f5f811ee202b6a1811fc80d874544e4e887d493655b3d5f1e26a16664ec23594e8e685b4201ff3d8c9e105", + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b010000000000000066e0e16b1f2817739e3b6f2c9c87086b31f86249123017fc159142eca323af28000000000000000000104000fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c37d406a857ee63ae9fecd4f293211c3e92c6847beb4871066ecfbca21f4a38607dad484d1fc66d48a45d08c1cec17d18889212fe90f204ee16684d701e5ebf4cb837bdd6dd0af53318a5465ac6f44a98f3769e7481e99169975af532a5614809" + ], + [ + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c0000000000000000000000000000000000000000000000000000000000000000000000000000000040420f0000000000000312001000497320616e796f6e652074686572653f882e98ec64ee02bb24da475a9dc9c491bb077b9fbd4cd32c90c73a253d1848e72c394c8a3fe09a0c620fcb5ad17a38d15b96620fd504be6c7dd57f25a889e20a" + ], + [ + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a2036872df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa10000000000000000000000000000000000000000000000000000000000000000000000000000000080841e000000000000032300200057656c636f6d652e204920616d20616e206175746f6d6174656420686f73742e0127f048da20a70e3abec01ff3cdf9c9563b4585ccd45c192e7d28a8ae98773910f796c7ffea6a735359c3d05699e25365dc6cad4d3d12f531758f088ada112c00" + ] + ], + "outcome": "refuse", + "reason": "ai_unmarked", + "at": [ + "record", + 2, + 0 + ] + }, + { + "name": "ai_unmarked_model", + "about": "an AI participant's chat that states origin 2 with a model other than its grant's (§9.2 step 6)", + "descriptor": "5752443101988ec20b60d2d23c4c475c1f4e623f115ffbe7659bd0c81c0af9233c28589285c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b00406cd8e95d060040420f0000000000150068747470733a2f2f726f6f6d732e6578616d706c650800000003003c0000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f00000000000504686f737407010102ae06000000000000000000000000000000000000000000000000000000000000096d6f64657261746f720001000108080000000000000000000000000000000000000000000000000000000000000861756469656e636500010100580000000000000000000000000000000000000000000000000000000000000005677565737403010100580100000000000000000000000000000000000000000000000000000000000007656e636f64657203000000000000000000000000000000000000000000000000000000000000000000000002057374616765020100000000046d61696e0202d007000002067370656563680101046f707573000001000000000000100000004000000080bb0000010f0a63616d6572612d37323002020d617630312e302e30354d2e30380b617663312e363430303146010002800268011e0096000000f40100000005d0021e00f4010000dc0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086c97e1a4b2609cb3438fd6c165e0bdc6f016bae7d16c4d37a387f4be3891b46f97c63fbf4814de11aadb3ee409d246a182017b43cacb6a6c64905bceb31c30b", + "grants": [ + "575247310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a2036872df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa100000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a0000000000070101020000000000000000000000000000000000000000000000000000000000000000005745503101ae060000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c6545000261694000383763386330653335666337666136613964346231393232336634303238326663333565396665633265353933383061633562336466616561653664353930385c245e4d368eea2fa46a37c069226e63a3489e51338486b574f405737169de74e0e230e5f27b523eb35cdc5e5a63fe4f104c48556ba46d8433b3db9a265eaa01", + "575247310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310158000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c650000da52cb8a9a33184a5da85c37affe7d9dfc6f4e7a3019ecc07c090358ded448066ac88859718562bb805f34d7da43880addb6098e654e8e51481afb9ee22ee40e" + ], + "key_log_head": "{\"grant_max_s\":86400,\"issued_at\":\"2026-10-15T00:00:00Z\",\"keys\":[{\"compromised_since\":null,\"id\":\"room-authority\",\"key\":\"988ec20b60d2d23c4c475c1f4e623f115ffbe7659bd0c81c0af9233c28589285\",\"may_sign\":[\"live/room\"],\"status\":\"active\",\"until\":null}],\"kind\":\"wai-key-set\",\"next_root\":\"3328656f3df5d9617e108d46080e23610712c9979d4437e706240f53d6cadf7b\",\"origin\":\"https://rooms.example\",\"prev\":null,\"revoked_modules\":[],\"root\":\"687972706312d9675734bf13b4bcf695ecb1b374de7809242aa91f130c6a1c0c\",\"seq\":0,\"sig\":\"ba55b157ebe88fc97f2b5a5e3556415563552ed88b9931060b9e99ae0a7decefdc63e2cb916b9a5d03c80a0c1823de14e7d8d3fe0b726c2464d823611780290a\",\"status_max_age_s\":3600}", + "chains": [ + [ + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001040002df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa13427f23d6605d4ed9be070c149aa749edd535e46fb4a813e47bdd750d546f9654022ff7403111b077255cc79fffeb37146f44c32b5f5f811ee202b6a1811fc80d874544e4e887d493655b3d5f1e26a16664ec23594e8e685b4201ff3d8c9e105", + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687c73c9d1dc51b9a83c180ec0bd17f1c8520c0ab70c4874225109cadb2d493ec1b010000000000000066e0e16b1f2817739e3b6f2c9c87086b31f86249123017fc159142eca323af28000000000000000000104000fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c37d406a857ee63ae9fecd4f293211c3e92c6847beb4871066ecfbca21f4a38607dad484d1fc66d48a45d08c1cec17d18889212fe90f204ee16684d701e5ebf4cb837bdd6dd0af53318a5465ac6f44a98f3769e7481e99169975af532a5614809" + ], + [ + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a203687fcb90cb92576cc567d08bedfa255bb94366c5b882a03ce247745dfd0e32ab09c0000000000000000000000000000000000000000000000000000000000000000000000000000000040420f0000000000000312001000497320616e796f6e652074686572653f882e98ec64ee02bb24da475a9dc9c491bb077b9fbd4cd32c90c73a253d1848e72c394c8a3fe09a0c620fcb5ad17a38d15b96620fd504be6c7dd57f25a889e20a" + ], + [ + "574556310191ac4a45ebdee375b286d920fb2d07f064a31e7b7243829126eee2e27a2036872df53b5dcc0c0ad294a87b904069ccadbde41494fa7e39cbf6720e1b2d5c5aa10000000000000000000000000000000000000000000000000000000000000000000000000000000080841e000000000000034300200057656c636f6d652e204920616d20616e206175746f6d6174656420686f73742e0267fa6ab069abc245aeccbc6afa47448ff1510522c9f9b7a79b7da263115ac1813dad0dd58243a1930f8b515255419da8042fb3bd634162e4d49a0eaa3c634dcc81144fe2cc83c3d0852e7a516af18e7f9deaa4883b4faf13af1ae9f150590302" + ] + ], + "outcome": "refuse", + "reason": "ai_unmarked", + "at": [ + "record", + 2, + 0 + ] + }, { "name": "rate_exceeded", "about": "a third event in one second under a grant of two per second (§4.1; live-events §6.3)", diff --git a/wai/live-conformance/synthesis/README.md b/wai/live-conformance/synthesis/README.md new file mode 100644 index 000000000..68e61abbf --- /dev/null +++ b/wai/live-conformance/synthesis/README.md @@ -0,0 +1,38 @@ +# live-conformance/synthesis — synthesis provenance + +The corpus of [`extensions/synthesis.md`](../../extensions/synthesis.md) §13, in one file, +`vectors.json`: the EU icons §10.2 pins (`eu_icons`, each its `file` and `sha256`), and the +**vectors**, each a case of §11.1 and what a verifier gives it. + +A case holds its `policy` (`dual_layer`, `assistive`); its records as JSON texts, list by list: +`claims` (origin claims), `markings`, `detections`, `reviews`, `label_policy` (a text or +`null`) and `player` (the log's entries); the `objects` it holds, each a `WAI1` envelope in +hexadecimal; the `decoded` samples it holds, each the content hash of the `object` decoded and +its canonical `samples` in hexadecimal; and a `room` or `null`: its `descriptor`, `grants`, +`key_log` and `chains` as live-rooms' corpus gives them. + +**Synthesis equivalence.** For each vector, a conforming verifier reaches the outcome it states: + +- `refuse`: the `reason` and where, `at`: `["claim", i]`, `["marking", i]`, `["detection", i]`, + `["review", i]`, `["label_policy"]`, `["player", i]`, or `["room", …]` with the room verifier's + own place (`["room", "record", c, i]` for record `i` of chain `c`); +- `accept`: the `statements` of §11.3: `claims`, each its `link`, `class`, `source`, `c2pa` (the + statement of §6, exactly as written) and `assistive` (`proved`, `not-recomputed`, `asserted`, or + `null` for a claim that is not an assist); `detections`, each its `subject` and `result`; + `ai_tracks`, each its `track` and `claim`; `reviews_presented`, each its log `entry` and + `review`; `labels`, each its log `entry`, `trigger` and the `shown` entry that met it; + `visual_icon`, the file of §10.2 the label policy's visual is, or `null`; and `room`, the room's + own outcome or `null`. + +A verifier that reaches another outcome, reason, place or statement does not conform. + +**Provenance.** `wai_synthesis_vectors gen` writes `vectors.json` from records authored in code: +every key is SHA-256 of the corpus's label and an index, every sample buffer and envelope payload +is SHA-256 output of a label, every time is a constant, and Ed25519 signing is deterministic, so +the file is the same bytes on every machine. The watermark scheme the markings and detections +name, `wai-mark:conformance`, names no algorithm (§7.4): the corpus exercises the records and the +agreement of the two layers, not a watermark. + +``` +python3 -I ../synthesis_verify.py . +``` diff --git a/wai/live-conformance/synthesis/vectors.json b/wai/live-conformance/synthesis/vectors.json new file mode 100644 index 000000000..838c58ee3 --- /dev/null +++ b/wai/live-conformance/synthesis/vectors.json @@ -0,0 +1,1552 @@ +{ + "about": "the conformance corpus of extensions/synthesis.md §13, written by wai_synthesis_vectors gen", + "eu_icons": [ + { + "file": "LABEL_AI GENERATED_black transparent.svg", + "sha256": "63d28ab55916b4548edff21d5fbcac065a13e5fb936e9b304ff0bada45f2fac1" + }, + { + "file": "LABEL_AI GENERATED_black.svg", + "sha256": "503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f" + }, + { + "file": "LABEL_AI GENERATED_white transparent.svg", + "sha256": "d19736f6da9d38af3cffce8ba08f3f7658b573e1794c173fcd8b55a5c200ed44" + }, + { + "file": "LABEL_AI GENERATED_white.svg", + "sha256": "10125cdef3fc60a351df72fe1266bb00ea046086921bd1bb20135f0f07b5ed36" + }, + { + "file": "LABEL_AI MODIFIED_black transparent.svg", + "sha256": "9ab09f54c1ccef01799af43794cd93a8af6607ba9ac6b9526fb90a02be8ddf3d" + }, + { + "file": "LABEL_AI MODIFIED_black.svg", + "sha256": "2e7349e5eca4ee78eeef160dfef4545c31850932245373a07c5401e73e6599c0" + }, + { + "file": "LABEL_AI MODIFIED_white transparent.svg", + "sha256": "7ccbce41f9f821a574007b32806ddb6d635ee8bbc5d753ccd05f66f5a3165845" + }, + { + "file": "LABEL_AI MODIFIED_white.svg", + "sha256": "0c72a7d569a1447e2982a843e156bd5f9cb2ab6f201943c6c68f988dc2f1bb6e" + }, + { + "file": "LABEL_AI_black transparent.svg", + "sha256": "f8e20f1dde8c7d95940da08960b1063620812acc9b4ea96eb5ad051f80f67c21" + }, + { + "file": "LABEL_AI_black.svg", + "sha256": "58ffe859a4d74829d397f534a988081bcefb716849c7d3b84fa7a026dd1d257f" + }, + { + "file": "LABEL_AI_white transparent.svg", + "sha256": "52d87cb3f6a191dba24796f745e1eeb80d342883acd1a2e6d856342ad853766c" + }, + { + "file": "LABEL_AI_white.svg", + "sha256": "5b3b94ae67fea55c4f2d013d0b8ed5b876c1533555ed0ed60e688f2f3ccf1a50" + }, + { + "file": "LABEL_AI GENERATED_black transparent.png", + "sha256": "74c8c4ee720c4a3286c3c1b9f216ac9b918ebf449e8c421dde5a40cd216b27e6" + }, + { + "file": "LABEL_AI GENERATED_black.png", + "sha256": "8d0af57bc93ba3797042a4b56db757d85f924d9b0200b75b8842da7be5e402a2" + }, + { + "file": "LABEL_AI GENERATED_white transparent.png", + "sha256": "06c4c7c00738e0e44c35f5927e72842ff7a9dd757e0374335b9cc1ba6bb52199" + }, + { + "file": "LABEL_AI GENERATED_white.png", + "sha256": "f139d8de4b5173f47a777431e7e03d9258d8ea7ee1ee1a062febc7e9fff067fc" + }, + { + "file": "LABEL_AI MODIFIED_black transparent.png", + "sha256": "f48f6c5137834fff87c701eb9a44934ac51c6b5d826b9b5cfead1b88024f8387" + }, + { + "file": "LABEL_AI MODIFIED_black.png", + "sha256": "0f32e4afbe1459baaf425c78373ea541421ecdb61803438892e2fc07df67da8f" + }, + { + "file": "LABEL_AI MODIFIED_white transparent.png", + "sha256": "1f3b435b0bf775a168309c4ede49b23c7fa94b759a72623932e6ccb7bdef6203" + }, + { + "file": "LABEL_AI MODIFIED_white.png", + "sha256": "64241cf4f87eb0837bd234cb63716c7d78f4e9dbbb8866f40464ca4e6bfbfead" + }, + { + "file": "LABEL_AI_black transparent.png", + "sha256": "24da93847c04a5c86c714c6cd018c45331b47df32936b617b3c8476933d97fa9" + }, + { + "file": "LABEL_AI_black.png", + "sha256": "0814d5f04533c118f81aed0d353ea75d1df4171c40b962dba1dfa044a3d84779" + }, + { + "file": "LABEL_AI_white transparent.png", + "sha256": "5b8c743b679a62453a4bc25070b82bb4db33ad9c68f4ef1b6ef7bb562dd3a4e3" + }, + { + "file": "LABEL_AI_white.png", + "sha256": "b9041e416fed455722a3aaa06a5ea0eb376d8a8e11385fdc8e3e8ee6998cdf26" + } + ], + "vectors": [ + { + "name": "chain", + "about": "a capture, a neural codec's reconstruction of it, and two pinned integer derivations of that reconstruction, each recomputed from its input's decoded samples and proved (§3, §4)", + "policy": { + "dual_layer": "when-given", + "assistive": "require-proof" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}", + "{\"class\":\"reconstructed\",\"evidence\":null,\"inputs\":[{\"class\":\"captured\",\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"origin\":\"e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai.neural.int_hyper\",\"oversight\":null,\"record\":null,\"sig\":\"6a098643b18a49fad29ee82395a5d4dfe51a0074fc213947cb09c3010e9ffc8bc02cda5871fdc8dab487390f3ecd50c12c4a06e735e5fc82b1053d1a5f015206\",\"signer_pubkey\":\"ff703397c2545c095bb3a790fde7399961c3076bbefcac542792dc4c00acdd6c\",\"source\":\"digitalCapture\",\"step\":\"convert\",\"subject\":{\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"type\":\"object\"}}", + "{\"class\":\"assistive\",\"evidence\":{\"derivation\":\"wai-synthesis:upscale2\",\"height\":3,\"input_samples\":\"6ee5006f705630e934216406a83b76d1115f02ce90970b633f68abede9a5d484\",\"output_samples\":\"9cf4a56cc146a782559d36953010f79ddfbc621a0178460d46ca00693652a2e4\",\"params\":{},\"tier\":\"replicate\",\"width\":4},\"inputs\":[{\"class\":\"reconstructed\",\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"origin\":\"4e09c93646b7a60b0321ca20646718f23ab4ea2712f7608d174544ac9c9a2cfb\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai-synthesis:upscale2\",\"oversight\":null,\"record\":null,\"sig\":\"a0d1198904652fedd4325ec7bf8096bc2684a86e4cec560354c499d5b5628b42ef9a7641f4924ae3ea883ca700dca0f44a2a863024b134574346535899814204\",\"signer_pubkey\":\"08af3636c89e55c66c0498aacb199aeb6b3e3f3fd25fc4cf42b699a4b86f1070\",\"source\":\"algorithmicallyEnhanced\",\"step\":\"assist\",\"subject\":{\"content_hash\":\"6579d92c519641fbaa6ac9d42e05c3b87f64c4237b32e268cb54d8588773f6e0\",\"type\":\"object\"}}", + "{\"class\":\"assistive\",\"evidence\":{\"derivation\":\"wai-synthesis:denoise3\",\"height\":3,\"input_samples\":\"6ee5006f705630e934216406a83b76d1115f02ce90970b633f68abede9a5d484\",\"output_samples\":\"69c0ec2d065f3818b39074d0046718e384e00616f69b33fcd41251b62e5d65cf\",\"params\":{},\"tier\":\"replicate\",\"width\":4},\"inputs\":[{\"class\":\"reconstructed\",\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"origin\":\"4e09c93646b7a60b0321ca20646718f23ab4ea2712f7608d174544ac9c9a2cfb\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai-synthesis:denoise3\",\"oversight\":null,\"record\":null,\"sig\":\"1a8c49f4478f946bfed801951643f70d7fbad455e7e0c11b18e14dda3c5c0d4b592c9e31525d59dc8ddff6a1c9b13f14cad27c57513ef5a48619a87b9c917f0b\",\"signer_pubkey\":\"08af3636c89e55c66c0498aacb199aeb6b3e3f3fd25fc4cf42b699a4b86f1070\",\"source\":\"algorithmicallyEnhanced\",\"step\":\"assist\",\"subject\":{\"content_hash\":\"bbeddbfbea7af91c2a151e23d18e24223bc5402d73438922f56d00d9cc9f3924\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "57414931650000007b22776169223a22312e30222c226d65646961223a22696d616765222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e696d6167652e706e67227d7d30000000adcacffa0e72ec5a3c7669181b08b5692cd12204987cc37e77269c1f412c0b9256006715ab5c2f20f8a0fdc97c291ff1", + "574149316c0000007b22776169223a22312e30222c226d65646961223a22696d616765222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6e657572616c2e696e745f6879706572227d7d28000000fe2b90bc63770f4fee58835d9961fa96dd35f8ef1b1d203d742fbe9b9c0762ff6a9fdda2fc33f61d", + "57414931650000007b22776169223a22312e30222c226d65646961223a22696d616765222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e696d6167652e706e67227d7d30000000ec9cec6032704eb18efb2f3025c6806383037212d32f92949f3cd2e053fb6750925d43e109c5e6c16de7f2564dce6d1e", + "57414931650000007b22776169223a22312e30222c226d65646961223a22696d616765222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e696d6167652e706e67227d7d3000000098703f795784510292cfa74ab16f4dffd69f99e7ec2c98d071220146b803a8015a945bf6b02b39ba9f81013a3b2ba2f5" + ], + "decoded": [ + { + "object": "5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2", + "samples": "8d03fc87a587bd2dc32a5d691b153f5dba03c6b445e757f9f960e1e1e7fccfed72f9b16c" + }, + { + "object": "6579d92c519641fbaa6ac9d42e05c3b87f64c4237b32e268cb54d8588773f6e0", + "samples": "8d03fc8a54c287a587a269a5bd2dc37445962a5d692a5d69540c9e635e7172b0459a9065c27184a5659b895ab1895ab11b153f3c68215dba0392b724c6b445d7869fe757f9e757f98a3b909586889fd180b5d16ecbd15cddaa87f084b3f084b3f960e1eda4efe1e7fcd8eab7cfed72e4cf6ff9b16cf9b16cf960e1eda4efe1e7fcd8eab7cfed72e4cf6ff9b16cf9b16c" + }, + { + "object": "bbeddbfbea7af91c2a151e23d18e24223bc5402d73438922f56d00d9cc9f3924", + "samples": "7430b38a6b8f95698f735893754a8a929173b49c7dbf7ba1c171bac4b9a7d4cd87ebac85" + } + ], + "room": null, + "outcome": "accept", + "statements": { + "claims": [ + { + "link": "e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31", + "class": "captured", + "source": "digitalCapture", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.created\",\"digitalSourceType\":\"http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture\"}],\"assertions\":[]}", + "assistive": null + }, + { + "link": "4e09c93646b7a60b0321ca20646718f23ab4ea2712f7608d174544ac9c9a2cfb", + "class": "reconstructed", + "source": "digitalCapture", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.opened\"},{\"action\":\"c2pa.converted\"}],\"assertions\":[]}", + "assistive": null + }, + { + "link": "3fc547996fadb46eedc47c44c00c8a232888a27e1aa1dbc2f41a1073fb2051e0", + "class": "assistive", + "source": "algorithmicallyEnhanced", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.opened\"},{\"action\":\"c2pa.edited\",\"digitalSourceType\":\"http://cv.iptc.org/newscodes/digitalsourcetype/algorithmicallyEnhanced\"}],\"assertions\":[]}", + "assistive": "proved" + }, + { + "link": "e0672490caf6fec9d6e974ba2878d3873b287a80ec6f3c1b0fb3e1135ebcb3f5", + "class": "assistive", + "source": "algorithmicallyEnhanced", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.opened\"},{\"action\":\"c2pa.edited\",\"digitalSourceType\":\"http://cv.iptc.org/newscodes/digitalsourcetype/algorithmicallyEnhanced\"}],\"assertions\":[]}", + "assistive": "proved" + } + ], + "detections": [], + "ai_tracks": [], + "reviews_presented": [], + "labels": [], + "visual_icon": null, + "room": null + } + }, + { + "name": "assistive_asserted", + "about": "a float super-resolution step that states the assistive class with attest evidence: asserted, not proved, under a policy that accepts assertions (§4.4)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}", + "{\"class\":\"reconstructed\",\"evidence\":null,\"inputs\":[{\"class\":\"captured\",\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"origin\":\"e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai.neural.int_hyper\",\"oversight\":null,\"record\":null,\"sig\":\"6a098643b18a49fad29ee82395a5d4dfe51a0074fc213947cb09c3010e9ffc8bc02cda5871fdc8dab487390f3ecd50c12c4a06e735e5fc82b1053d1a5f015206\",\"signer_pubkey\":\"ff703397c2545c095bb3a790fde7399961c3076bbefcac542792dc4c00acdd6c\",\"source\":\"digitalCapture\",\"step\":\"convert\",\"subject\":{\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"type\":\"object\"}}", + "{\"class\":\"assistive\",\"evidence\":{\"derivation\":\"super-resolution\",\"height\":3,\"input_samples\":\"6ee5006f705630e934216406a83b76d1115f02ce90970b633f68abede9a5d484\",\"output_samples\":\"e9aebdaebbd8ad1ef951b3d2d0cc62272cfcd73f1b69d11363d361826f458b2d\",\"params\":{\"scale\":2},\"tier\":\"attest\",\"width\":4},\"inputs\":[{\"class\":\"reconstructed\",\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"origin\":\"4e09c93646b7a60b0321ca20646718f23ab4ea2712f7608d174544ac9c9a2cfb\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[{\"digest\":\"e2ea8c07e5e2e2c846c97de6f5f79ab21593563603420e70736ab7f22ca451d4\",\"type\":\"trained\"}],\"operation\":\"super-resolution\",\"oversight\":null,\"record\":null,\"sig\":\"8f3ea73024ea875df6ec23ea0f79b33e760ebb2036b356ad6d2f8813a620b07ddfc27d8e6d2ece330a82db3d5df571c80b3b32880a9bb8e480d12bce3d3e130b\",\"signer_pubkey\":\"08af3636c89e55c66c0498aacb199aeb6b3e3f3fd25fc4cf42b699a4b86f1070\",\"source\":\"algorithmicallyEnhanced\",\"step\":\"assist\",\"subject\":{\"content_hash\":\"d4b33e2b9bad56645fcf0b7856b9009fecd2b4e5e587eeb791a0da87daa601ce\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "accept", + "statements": { + "claims": [ + { + "link": "e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31", + "class": "captured", + "source": "digitalCapture", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.created\",\"digitalSourceType\":\"http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture\"}],\"assertions\":[]}", + "assistive": null + }, + { + "link": "4e09c93646b7a60b0321ca20646718f23ab4ea2712f7608d174544ac9c9a2cfb", + "class": "reconstructed", + "source": "digitalCapture", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.opened\"},{\"action\":\"c2pa.converted\"}],\"assertions\":[]}", + "assistive": null + }, + { + "link": "efe4ca228b9ffa4bc4e57ec985a723f1b66804cefe3d8badfe4085d26757d950", + "class": "assistive", + "source": "algorithmicallyEnhanced", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.opened\"},{\"action\":\"c2pa.edited\",\"digitalSourceType\":\"http://cv.iptc.org/newscodes/digitalsourcetype/algorithmicallyEnhanced\"}],\"assertions\":[{\"data\":{\"modelIdentifier\":\"tag:transaction.science,2026:wai/model/e2ea8c07e5e2e2c846c97de6f5f79ab21593563603420e70736ab7f22ca451d4\",\"modelType\":\"c2pa.types.model\"},\"label\":\"c2pa.ai-disclosure\"}]}", + "assistive": "asserted" + } + ], + "detections": [], + "ai_tracks": [], + "reviews_presented": [], + "labels": [], + "visual_icon": null, + "room": null + } + }, + { + "name": "generated", + "about": "a generated video, a pinned algorithmic audio generation, an inpainted capture reviewed by a person, a composite of a capture and the generated video, and a transcode of it; each synthesized object marked in both layers, a detection agreeing with the video's claim, and one finding no mark on the capture (§5, §6, §7)", + "policy": { + "dual_layer": "required", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}", + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}", + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"audio\",\"models\":[{\"digest\":\"b47ce1b548507dcf199243a31dec452a7674e16181cbc6aa5c9d6ce8e729568e\",\"type\":\"algorithmic\"}],\"operation\":\"wai.generate.spectral\",\"oversight\":\"prompt_guided\",\"record\":null,\"sig\":\"93bf400b641704b0bc8599714bd9df962908c842cc9ec807fc74b8c3e44285e259d6c54b616a076e68ea8f8725e6bdb5792a804fb0082ddc0cc19b295b596a08\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"algorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"4df75d76e850e6e24b6c19a3c6ca3e8fc6f14ad5029e80ba05c4076baa07a917\",\"type\":\"object\"}}", + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[{\"class\":\"captured\",\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"origin\":\"e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[{\"digest\":\"8cf24466f8c423296347a21fe427b09a94beb668ba22aac59a5173abf4a9517c\",\"type\":\"trained\"}],\"operation\":\"inpainting\",\"oversight\":\"human_validated\",\"record\":null,\"sig\":\"ab65ae0639749dd7ed50886934677d969d33cd53567ca7bd2d07f4fb8f2563313e8ba5aef66a5588a25582ac2034ed31b6d9b4b81fad050dd4fa0c01ca1ee20b\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"compositeWithTrainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"501e6fb06de91e323282c5a8afd400d21adcabd99576bbc7a8b6b5fccf05524a\",\"type\":\"object\"}}", + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[{\"class\":\"captured\",\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"origin\":\"e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31\",\"source\":\"digitalCapture\"},{\"class\":\"synthesized\",\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"source\":\"trainedAlgorithmicMedia\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai.derive.overlay_composition\",\"oversight\":null,\"record\":null,\"sig\":\"d66cb56f05da99a765ae25b5f65464a70c4295ae8f091c4d8f9ae93a781439245451a8c540b8790c0f56ed1bc2215efd0bc556430a45e67ccb97476476998107\",\"signer_pubkey\":\"08af3636c89e55c66c0498aacb199aeb6b3e3f3fd25fc4cf42b699a4b86f1070\",\"source\":\"compositeSynthetic\",\"step\":\"convert\",\"subject\":{\"content_hash\":\"19db0b61e5caf49013bb45bed4e9d77f80abe99a4957e3b9a758c87a47ed41a1\",\"type\":\"object\"}}", + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[{\"class\":\"synthesized\",\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"source\":\"trainedAlgorithmicMedia\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[],\"operation\":\"wai.video.hevc\",\"oversight\":null,\"record\":null,\"sig\":\"1b766b2c66a402d8f0a5d3a79fd2a576060781f603e0f6b105a5400bc6b1358c1c096d2d51fe8fa469b7367907fd1cff54cfaa9e4b1000fccb89ea41e8b63a0f\",\"signer_pubkey\":\"ff703397c2545c095bb3a790fde7399961c3076bbefcac542792dc4c00acdd6c\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"convert\",\"subject\":{\"content_hash\":\"c61cb9797fc8bd007459550e2e21ab4989c36d3b26f4d7d10773dd03184621cd\",\"type\":\"object\"}}" + ], + "markings": [ + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-marking\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"payload\":\"03ead74abe77e324\",\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"1c6c99343e99185ae70d5c0d496628cbaa7e00bd50c6539f1a658ab82e663cbe9ea2302b9c125db6005fa5da8eb837f23f843f0e26af975ca63b794c8942610a\",\"signer_pubkey\":\"a56ef30c04f5b691ee3a2f648d5f5036cfed6b7db6514a23122c75e02deccf7c\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\"}", + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-marking\",\"origin\":\"08c12d093fe7df981379a7bd0ba70f8b9fb490a705c987e1ed13f98b16b23663\",\"payload\":\"0308c12d093fe7df\",\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"551cebcec062e1e13ce1def1f411e4be23f1462ee02abcc2b847bd456a94ff65b7013e7bf0922f1c1239f6b6a9afd9f830cfa13a3d545d914848668c43e01f0e\",\"signer_pubkey\":\"a56ef30c04f5b691ee3a2f648d5f5036cfed6b7db6514a23122c75e02deccf7c\",\"subject\":\"4df75d76e850e6e24b6c19a3c6ca3e8fc6f14ad5029e80ba05c4076baa07a917\"}", + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-marking\",\"origin\":\"e7a086610eb064e991d86cf59ca2239c6a1cadda6e824140efe8c1530898aef5\",\"payload\":\"03e7a086610eb064\",\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"ac99705a34d7f3c76e50f8ed8a18d4d30dfe59230220561bae5bb581656bbedc77b1bc343af487db21c68f5028b0399289818fcaa8fc3128f3de62e62ffe330e\",\"signer_pubkey\":\"a56ef30c04f5b691ee3a2f648d5f5036cfed6b7db6514a23122c75e02deccf7c\",\"subject\":\"501e6fb06de91e323282c5a8afd400d21adcabd99576bbc7a8b6b5fccf05524a\"}", + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-marking\",\"origin\":\"9dec524cf50ebf41ac92d3531d657d30ffe0b9c68c14aed8641b1a5c6affb014\",\"payload\":\"039dec524cf50ebf\",\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"0569b219a150db512882b331bf4a7fb17aee13c9494904f5f783cbf6bf405a98a68f56574b6bd11a5e41d3f463ca4206d60eae8d31cdb8f7019a9ab92e4dff0a\",\"signer_pubkey\":\"a56ef30c04f5b691ee3a2f648d5f5036cfed6b7db6514a23122c75e02deccf7c\",\"subject\":\"19db0b61e5caf49013bb45bed4e9d77f80abe99a4957e3b9a758c87a47ed41a1\"}", + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-marking\",\"origin\":\"bbf4d18b4bdf0851e5bcdf02d2ef994a8cdece8e4f160b6cd25a0d4bc3f1a14d\",\"payload\":\"03bbf4d18b4bdf08\",\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"b209591736654bb122938823a1aa1ad6a10c6e09cf12494a67be06175ddb7a06ddf1c31495c8ef054d6a35e48f9eef4f3c961e9730b3ea59226d79c0feffc20b\",\"signer_pubkey\":\"a56ef30c04f5b691ee3a2f648d5f5036cfed6b7db6514a23122c75e02deccf7c\",\"subject\":\"c61cb9797fc8bd007459550e2e21ab4989c36d3b26f4d7d10773dd03184621cd\"}" + ], + "detections": [ + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-detection\",\"payload\":\"03ead74abe77e324\",\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"f10cf6cdf66a82d84042dc410bda131a98f5ef21730b4d56ee473ebf3c599efdcf78850458060f68db43054b270e149ffc9af1f113035286af3f25f33067c40f\",\"signer_pubkey\":\"a4ddb8268591a53a9ee6446a9ee8d5917fa7b337a65f806b9d12708cf19322c6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\"}", + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-detection\",\"payload\":null,\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"af7d6d6160203509f907d0f592cba7b2199cd9fa58f2c8d282273c4511cfd525e0a9f36a7bbd96141bb126bb5985f1e3f3f25626005aa5988a9906c98fd2b604\",\"signer_pubkey\":\"a4ddb8268591a53a9ee6446a9ee8d5917fa7b337a65f806b9d12708cf19322c6\",\"subject\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\"}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "57414931650000007b22776169223a22312e30222c226d65646961223a22696d616765222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e696d6167652e706e67227d7d30000000adcacffa0e72ec5a3c7669181b08b5692cd12204987cc37e77269c1f412c0b9256006715ab5c2f20f8a0fdc97c291ff1", + "57414931660000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617631227d7d400000001c8af660db156fd4777ceafd936d90bf46e4ea9d79272f80bbcfdce23a6da360aa0772307063ca56ee4d1d2b75d5de565c3895a9c230ba3facfad6d1579caf53", + "574149316a0000007b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e67656e65726174652e737065637472616c227d7d0a0000007b22666f726d223a307d", + "57414931650000007b22776169223a22312e30222c226d65646961223a22696d616765222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e696d6167652e706e67227d7d3000000019e6efa99b020862b987bc8b1f342201b75f1dcbf6957eff0cc3f89482b7d9a5009a19d526c1a75e9ef3d303b8baaa94", + "57414931650000007b22776169223a22312e30222c226d65646961223a22696d616765222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e696d6167652e706e67227d7d30000000b63b86d4ec9dd56c4dac4146be2804b51b5869d862a98670ae00d52261c72ac3f1df18647863e8cdeabff46339b9c24c", + "57414931670000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e68657663227d7d4000000094e921099bea0c893bce9d14f037d535670e1f3f3e04ef819bb5aaa20f70edac4d3d7518e875ab1bc990c7524dcc4d6731add333db596ef78a24984f9cd87bb8" + ], + "decoded": [], + "room": null, + "outcome": "accept", + "statements": { + "claims": [ + { + "link": "e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31", + "class": "captured", + "source": "digitalCapture", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.created\",\"digitalSourceType\":\"http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture\"}],\"assertions\":[]}", + "assistive": null + }, + { + "link": "ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0", + "class": "synthesized", + "source": "trainedAlgorithmicMedia", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.created\",\"digitalSourceType\":\"http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia\"}],\"assertions\":[{\"data\":{\"contentProfile\":{\"humanOversightLevel\":\"fully_autonomous\"},\"modelIdentifier\":\"tag:transaction.science,2026:wai/model/1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"modelType\":\"c2pa.types.model\"},\"label\":\"c2pa.ai-disclosure\"}]}", + "assistive": null + }, + { + "link": "08c12d093fe7df981379a7bd0ba70f8b9fb490a705c987e1ed13f98b16b23663", + "class": "synthesized", + "source": "algorithmicMedia", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.created\",\"digitalSourceType\":\"http://cv.iptc.org/newscodes/digitalsourcetype/algorithmicMedia\"}],\"assertions\":[]}", + "assistive": null + }, + { + "link": "e7a086610eb064e991d86cf59ca2239c6a1cadda6e824140efe8c1530898aef5", + "class": "synthesized", + "source": "compositeWithTrainedAlgorithmicMedia", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.opened\"},{\"action\":\"c2pa.edited\",\"digitalSourceType\":\"http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia\"}],\"assertions\":[{\"data\":{\"contentProfile\":{\"humanOversightLevel\":\"human_validated\"},\"modelIdentifier\":\"tag:transaction.science,2026:wai/model/8cf24466f8c423296347a21fe427b09a94beb668ba22aac59a5173abf4a9517c\",\"modelType\":\"c2pa.types.model\"},\"label\":\"c2pa.ai-disclosure\"}]}", + "assistive": null + }, + { + "link": "9dec524cf50ebf41ac92d3531d657d30ffe0b9c68c14aed8641b1a5c6affb014", + "class": "synthesized", + "source": "compositeSynthetic", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.created\",\"digitalSourceType\":\"http://cv.iptc.org/newscodes/digitalsourcetype/compositeSynthetic\"}],\"assertions\":[]}", + "assistive": null + }, + { + "link": "bbf4d18b4bdf0851e5bcdf02d2ef994a8cdece8e4f160b6cd25a0d4bc3f1a14d", + "class": "synthesized", + "source": "trainedAlgorithmicMedia", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.opened\"},{\"action\":\"c2pa.converted\"}],\"assertions\":[]}", + "assistive": null + } + ], + "detections": [ + { + "subject": "66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5", + "result": "agree" + }, + { + "subject": "69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681", + "result": "unmarked" + } + ], + "ai_tracks": [], + "reviews_presented": [], + "labels": [], + "visual_icon": null, + "room": null + } + }, + { + "name": "ai_room", + "about": "a room whose AI host's grant names its model: its chat and caption state that model, and its published speech track has a synthesized track claim naming it (§8)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"audio\",\"models\":[{\"digest\":\"857f14d62ade478f90d495a17495e1aa6608252205fe1b4e0bd43dfcf1456761\",\"type\":\"trained\"}],\"operation\":\"speech-synthesis\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"7e7b5b7821bc7928d1e8ae3ae2e25b2149ed3a624a6217db003658b9890afd9373c61d7d4d68e8dceb3664ee077006397b95ee3d2e0171caf73bc66bd6edfc09\",\"signer_pubkey\":\"26e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e741\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"from_group\":0,\"track\":\"e0f5f40db551bfb20f862d5d6ffd64dbea588d2181d8dfe58849b4d9f15931a6\",\"type\":\"track\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": { + "descriptor": "5752443101ba07665226a6cc29262167d963816c0949855436567469a282299e19951d15d86d1383a5809797241ba62c20bb2610b653449442b011405f246603c702124cc400406cd8e95d060040420f00000000000000080000000200000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f00000000000204686f73740101000008040000000000000000000000000000000000000000000000000000000000000861756469656e63650001000018000000000000000000000000000000000000000000000000000000000000000001067370656563680101046f707573000001000000000000100000004000000080bb0000010f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008951f673cfa31eaf7f5b68c7755826574bdf858e85925f8ab4ae2122db97b97ce4f0359e91ccefc8caec38d6ce9b2e90d070141e68d23503d93ba41a40c6b904", + "grants": [ + "5752473101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e126e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e74100000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000001010000000000000000000000000000000000000000000000000000000000000000000000574550310108040000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000100011568747470733a2f2f72656c61792e6578616d706c65450002616940003835376631346436326164653437386639306434393561313734393565316161363630383235323230356665316234653062643433646663663134353637363173f388e6e8845b7c383256ed852a5d354251fac217b9aa5cf1f2021719c1dede958544a127652754264fdf2379ff4dabeb0afe0862afc9674b314239a7ccc10a", + "5752473101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e1332dd253be02fa4f9d861626e9e09ad19786534665f684b833f5beaa94075f5400000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010000000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c6500001903d5c5c1f8f10ba284c91c71382d59f316181816ae9a2bb9e232a1fcf7cc40000afe69527553f25c996d3bd3ab0392be40c1baaafbb83ed8b1488207213206" + ], + "key_log": null, + "chains": [ + [ + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e16d1383a5809797241ba62c20bb2610b653449442b011405f246603c702124cc40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010400026e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e7417b98c0c1a0e637327a2d6d5f5fd46e63bb2a5970f6c67586cd7de6f575b6360f92c58afd19b1590b027705e137e7fd9113ec862f4a46f7cf98af22cddf15f6ab0a3d797c19ac7bac710280be04e85315ebb15d4a498b376ba1cf4fa5ef760d0a", + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e16d1383a5809797241ba62c20bb2610b653449442b011405f246603c702124cc40100000000000000e30951a58b352a71167f2653d553a499f42535929d2c70a2df6707d9237813e9000000000000000000104000332dd253be02fa4f9d861626e9e09ad19786534665f684b833f5beaa94075f5469e9ac354d85ed0e33615f6021464551a549219747f1887e8bd0ba8e198a417289544d1a426e53d808c6e39e490a36f4d7bbbb293038f456841cfe524fb294695727a8c1e1261c0274f4268369b32026b31ae73bfadb3f5c1fdc72341ac65604", + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e16d1383a5809797241ba62c20bb2610b653449442b011405f246603c702124cc40200000000000000f01481d18e4abd33b4feb973fad90407428633775ee248a3ee1ba5804452b24740420f00000000000012240026e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e741000100010011eec15a7256a01e340ad71d4ca262cbbd3b5d14b89767610b584fe8a879aaf56e8d567369bc64313dc8857a695c44935af619b0ebb63e9a4fbc1d8847130e" + ], + [ + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e1332dd253be02fa4f9d861626e9e09ad19786534665f684b833f5beaa94075f540000000000000000000000000000000000000000000000000000000000000000000000000000000080841e000000000000030800060048656c6c6f3f53e502ef3d4f09c4a615c8374bc433e727bf8bee9a34083ee5ba47ba2bbe463e96959334a02fd78310e063b5ee409626258cbf429e480c30b85f82349c13890a" + ], + [ + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e126e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e74100000000000000000000000000000000000000000000000000000000000000000000000000000000c0c62d000000000000034600230048656c6c6f2e204920616d20616e206175746f6d6174656420617373697374616e742e02857f14d62ade478f90d495a17495e1aa6608252205fe1b4e0bd43dfcf1456761290bea86e3e37ec17ddaa56e7eb3021ac46aca24e23452fb610549bb3599587822fbc702af39ca05d47e519f6a5bf1aecb37e28b2c784abce201d54026a70105", + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e126e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e741010000000000000052c8b1b099988b7700357fdc8c43b4fb67a8174eab58d664b5490ae5a29ec70200093d0000000000000a3000d007000002857f14d62ade478f90d495a17495e1aa6608252205fe1b4e0bd43dfcf145676102656e060048656c6c6f2ebca90ce1ee985ff5ab8d0c6d4e88fed39c76116454f6ce251b82f9b311cff3eb4440ffd246a25255b26a36529eeae05baab1f489ec4f22cc2cce45ce3bc6d306" + ] + ] + }, + "outcome": "accept", + "statements": { + "claims": [ + { + "link": "850549908658ec3921abee64bb94fcfad402b22efcf1b0d6915ae61bf15f8738", + "class": "synthesized", + "source": "trainedAlgorithmicMedia", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.created\",\"digitalSourceType\":\"http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia\"}],\"assertions\":[{\"data\":{\"contentProfile\":{\"humanOversightLevel\":\"fully_autonomous\"},\"modelIdentifier\":\"tag:transaction.science,2026:wai/model/857f14d62ade478f90d495a17495e1aa6608252205fe1b4e0bd43dfcf1456761\",\"modelType\":\"c2pa.types.model\"},\"label\":\"c2pa.ai-disclosure\"}]}", + "assistive": null + } + ], + "detections": [], + "ai_tracks": [ + { + "track": "e0f5f40db551bfb20f862d5d6ffd64dbea588d2181d8dfe58849b4d9f15931a6", + "claim": "850549908658ec3921abee64bb94fcfad402b22efcf1b0d6915ae61bf15f8738" + } + ], + "reviews_presented": [], + "labels": [], + "visual_icon": null, + "room": "accept" + } + }, + { + "name": "certified", + "about": "a review of a generated video at decode-equivalence, presented by a player whose decode path holds that tier and whose output is the reconstruction reviewed; the label shown at first exposure, after an ad break and within the interval (§9, §10)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [ + "{\"decoder\":\"builtin:wai@0.3.0\",\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-review\",\"notes\":null,\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"reconstruction\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"role\":null,\"scope\":\"rendering\",\"sig\":\"a83e554b18fb64e19b394adbd5c866b30ee79de5583e1a083b1187c6c30aa42396ca1a67e755e6cb7f45ce307caa2d9d054903d2c71c59731b32b46dc3629b0b\",\"signer_pubkey\":\"50999f876122d351b1a229a604ec946ddd24838b9e10d2468259405fe4695220\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"variant\":null,\"verdict\":\"approved\"}" + ], + "label_policy": "{\"audio\":\"6e0d3d5e9f9661b8c2327e57ebe1b360b204e109def81020a485edb8bf2f58e8\",\"classes\":[\"synthesized\"],\"grace_us\":500000,\"interval_us\":30000000,\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-label-policy\",\"sig\":\"50693722b2490992604330c305b695ac8ce99b2e4ac291dadbedcc9a0d9e128f965ee347a360675481d441f67145b5dd22417345274b9b337953ebb23479d300\",\"signer_pubkey\":\"34e2cdfcf44eaae9f3974cd801473ae99e6779b7e3d432fc12ff35f26d429948\",\"visual\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\"}", + "player": [ + "{\"at\":0,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"first-exposure\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"1071b9e5bc259e948b29c4b6e79c76a4749000d0998f97bb218160faac47a925d8df36ed6fe62a3586c8adae3c3ba32dbc21ad1434bed9c3c00c424b1962950f\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"d43d753b42fa00b30b4bde2820034298f165e27cf9b1b1b8c78e99131e1c8d8a\",\"seq\":1,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"fe343689ded585d0cb6c9b78e508152dd3f537125c2634bdc0c53b53402d7db14af0aac5431c8a24059d5f005d8d501f1cd68d1ff2bfa62fde8858a945506f01\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":1000000,\"entry\":{\"review\":\"f53388638eec4d3c50ad26873a266d51a5468f2b9df922e30d93e3980fa60282\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"review\"},\"kind\":\"wai-player\",\"prev\":\"780d0b5b9cb2a9439625ed2ae4cd88fa58078cd8faf9a93ce698f415467a79a3\",\"seq\":2,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"b3b382c0944fb868922b89d145974a98f62133a5a6053492709c4e1fc80ffc35184daf10df5414984f93fb345383fc93cc0247e80f0d0dcd06e4fa504dfac800\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20000000,\"entry\":{\"after\":\"ad-break\",\"type\":\"resume\"},\"kind\":\"wai-player\",\"prev\":\"8798772ab497744055c606abf9512a977710114b1d2e823fe8feb41e87ff94b1\",\"seq\":3,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"07605ee164d8b229cc90d97cee85ae4f16024b2ebd8d4ed36a1b2cd71f3c6b0dbbed9c2bbe2aecd99047c0f9bce48dbcf22fab06121382c3bfbd750400b89b09\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20200000,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"resume\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":\"e74865cf7556384fb5f978b1c89baa512762d79a4dda6e5fd3de460f211a46be\",\"seq\":4,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"082fb36c2f63a351bd2e6a5a1d697f300cda5a33405d08bf42a9d79f2c1e2579735d4ab1dd46cb1e2d51c25ab601135117df90ebdc34d61516cbcabf4798d802\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20200000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"0ce999210725d04111c829732c363e5587c1a13eb6990a74bf6ab09b3e4faa8f\",\"seq\":5,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"66ba5826145a6d7a17ed89e8bf8faa725bb34942afb3e86046a4d7380c57f8e7fec3bb6d3e55d01e76927065f13536b86a061eec0ba776de732462e61ed6940d\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":40000000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"4c772443e1c5b98c79ff193646ec9ee9ef00062f0bb8ab89330d08088e281686\",\"seq\":6,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"4429d3e146c5f24ad739037848fe8531a5978b062632351c231b5a827e9463b4390143db6d7a4acfe9b2f0234dd4826baa60930422ab2af65746d34716ca1a0f\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}" + ], + "objects": [ + "57414931660000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617631227d7d400000001c8af660db156fd4777ceafd936d90bf46e4ea9d79272f80bbcfdce23a6da360aa0772307063ca56ee4d1d2b75d5de565c3895a9c230ba3facfad6d1579caf53" + ], + "decoded": [ + { + "object": "66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5", + "samples": "c5ab2471427ff3fc018ffd5106fede9fa4155895fffa97099b03775d4b1a277cfb628b04" + } + ], + "room": null, + "outcome": "accept", + "statements": { + "claims": [ + { + "link": "ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0", + "class": "synthesized", + "source": "trainedAlgorithmicMedia", + "c2pa": "{\"actions\":[{\"action\":\"c2pa.created\",\"digitalSourceType\":\"http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia\"}],\"assertions\":[{\"data\":{\"contentProfile\":{\"humanOversightLevel\":\"fully_autonomous\"},\"modelIdentifier\":\"tag:transaction.science,2026:wai/model/1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"modelType\":\"c2pa.types.model\"},\"label\":\"c2pa.ai-disclosure\"}]}", + "assistive": null + } + ], + "detections": [], + "ai_tracks": [], + "reviews_presented": [ + { + "entry": 2, + "review": "f53388638eec4d3c50ad26873a266d51a5468f2b9df922e30d93e3980fa60282" + } + ], + "labels": [ + { + "entry": 1, + "trigger": "first-exposure", + "shown": 0 + }, + { + "entry": 5, + "trigger": "resume", + "shown": 4 + }, + { + "entry": 6, + "trigger": "interval", + "shown": 4 + } + ], + "visual_icon": "LABEL_AI GENERATED_black.svg", + "room": null + } + }, + { + "name": "record_malformed", + "about": "an origin claim with a member §3.1 does not list", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"note\":\"an extra member\",\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "record_malformed", + "at": [ + "claim", + 0 + ] + }, + { + "name": "record_malformed_step", + "about": "a capture that names an input: a capture has none (§3.2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[{\"class\":\"reconstructed\",\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"origin\":\"4e09c93646b7a60b0321ca20646718f23ab4ea2712f7608d174544ac9c9a2cfb\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"0bdc921c0bff9e00a34c2ff518feee86373a682472262ca386b9907cb4b8d271a1c3a8350b2b8e11e467346de7eba1bd944825b8d2632f133016410dbd785507\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "record_malformed", + "at": [ + "claim", + 0 + ] + }, + { + "name": "bad_signature", + "about": "an origin claim whose signature has one digit changed", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"00a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "claim", + 0 + ] + }, + { + "name": "bad_key", + "about": "an origin claim whose key is the identity point, of small order (SPEC §7 *Signatures* step 2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"0100000000000000000000000000000000000000000000000000000000000000\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "bad_key", + "at": [ + "claim", + 0 + ] + }, + { + "name": "claim_conflict", + "about": "two origin claims on one object", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}", + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T10:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"5c2b64f7b8a7035ef656f79daf42493c77491bfd036a80705ef217670a413a327f7960893782cd48a2b347021c0234fc53b48cc69de7a2eaefd6c574303ca505\",\"signer_pubkey\":\"ff703397c2545c095bb3a790fde7399961c3076bbefcac542792dc4c00acdd6c\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "claim_conflict", + "at": [ + "claim", + 1 + ] + }, + { + "name": "class_mismatch", + "about": "a transcode of a generated video that states the class reconstructed: a conversion keeps its input's class (§3.2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}", + "{\"class\":\"reconstructed\",\"evidence\":null,\"inputs\":[{\"class\":\"synthesized\",\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"source\":\"trainedAlgorithmicMedia\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[],\"operation\":\"wai.video.hevc\",\"oversight\":null,\"record\":null,\"sig\":\"bf6e0e7da44528f7036d1901604b9a9f385de17848ba725f89c186a7241749e51b1888e41093401bd70cacbddf1106529153e9dec8d72423967eb2b1ce5c6d07\",\"signer_pubkey\":\"ff703397c2545c095bb3a790fde7399961c3076bbefcac542792dc4c00acdd6c\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"convert\",\"subject\":{\"content_hash\":\"c61cb9797fc8bd007459550e2e21ab4989c36d3b26f4d7d10773dd03184621cd\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "class_mismatch", + "at": [ + "claim", + 1 + ] + }, + { + "name": "source_mismatch", + "about": "a codec's reconstruction of a capture that states digitalCreation: a conversion keeps its input's source (§5)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}", + "{\"class\":\"reconstructed\",\"evidence\":null,\"inputs\":[{\"class\":\"captured\",\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"origin\":\"e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai.neural.int_hyper\",\"oversight\":null,\"record\":null,\"sig\":\"54c54b2c87c00dddef409f18d9fc44eb99000f65824d7c707a7505e95d34e040823845d7d482ffffaa5e1891b3eacfe6f745ed07edccef99e3bbd7938926800b\",\"signer_pubkey\":\"ff703397c2545c095bb3a790fde7399961c3076bbefcac542792dc4c00acdd6c\",\"source\":\"digitalCreation\",\"step\":\"convert\",\"subject\":{\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "source_mismatch", + "at": [ + "claim", + 1 + ] + }, + { + "name": "input_mismatch", + "about": "a reconstruction that restates its input's class as reconstructed where the input's own claim says captured (§3.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}", + "{\"class\":\"reconstructed\",\"evidence\":null,\"inputs\":[{\"class\":\"reconstructed\",\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"origin\":\"e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai.neural.int_hyper\",\"oversight\":null,\"record\":null,\"sig\":\"22f75bacc7e121dac69c6c24b5654a2b4323e23d768d040df8115eddf02cccd723bd7438071d7189c9dd85d7728c76b0177f6d8fe860fd44858a238354e68405\",\"signer_pubkey\":\"ff703397c2545c095bb3a790fde7399961c3076bbefcac542792dc4c00acdd6c\",\"source\":\"digitalCapture\",\"step\":\"convert\",\"subject\":{\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "input_mismatch", + "at": [ + "claim", + 1 + ] + }, + { + "name": "class_understated", + "about": "a synthesized object labelled captured: the object is a generator's envelope (wai.generate.spectral, intent create), and its claim is a capture (§3.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"716ff62326cbb6e07cc61705672bac858cdadaf916bd05c49e66e7b28bef058e180d507460a0db3faa7dfed11eda5ed50ef6a27884024688218ef15d9869470d\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"4df75d76e850e6e24b6c19a3c6ca3e8fc6f14ad5029e80ba05c4076baa07a917\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149316a0000007b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e67656e65726174652e737065637472616c227d7d0a0000007b22666f726d223a307d" + ], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "class_understated", + "at": [ + "claim", + 0 + ] + }, + { + "name": "evidence_invalid", + "about": "replicate evidence naming a derivation §4 does not pin", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}", + "{\"class\":\"reconstructed\",\"evidence\":null,\"inputs\":[{\"class\":\"captured\",\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"origin\":\"e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai.neural.int_hyper\",\"oversight\":null,\"record\":null,\"sig\":\"6a098643b18a49fad29ee82395a5d4dfe51a0074fc213947cb09c3010e9ffc8bc02cda5871fdc8dab487390f3ecd50c12c4a06e735e5fc82b1053d1a5f015206\",\"signer_pubkey\":\"ff703397c2545c095bb3a790fde7399961c3076bbefcac542792dc4c00acdd6c\",\"source\":\"digitalCapture\",\"step\":\"convert\",\"subject\":{\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"type\":\"object\"}}", + "{\"class\":\"assistive\",\"evidence\":{\"derivation\":\"wai-synthesis:upscale3\",\"height\":3,\"input_samples\":\"6ee5006f705630e934216406a83b76d1115f02ce90970b633f68abede9a5d484\",\"output_samples\":\"9cf4a56cc146a782559d36953010f79ddfbc621a0178460d46ca00693652a2e4\",\"params\":{},\"tier\":\"replicate\",\"width\":4},\"inputs\":[{\"class\":\"reconstructed\",\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"origin\":\"4e09c93646b7a60b0321ca20646718f23ab4ea2712f7608d174544ac9c9a2cfb\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai-synthesis:upscale2\",\"oversight\":null,\"record\":null,\"sig\":\"a33cf2459e891d488eb1f70c6e09629535b6267bb119636abfb6bee6a2cb0158c4634e9c6ede2a2492e6fc1deb4c0fd1e0cca48593c9062ab7905e96a4b43208\",\"signer_pubkey\":\"08af3636c89e55c66c0498aacb199aeb6b3e3f3fd25fc4cf42b699a4b86f1070\",\"source\":\"algorithmicallyEnhanced\",\"step\":\"assist\",\"subject\":{\"content_hash\":\"6579d92c519641fbaa6ac9d42e05c3b87f64c4237b32e268cb54d8588773f6e0\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "evidence_invalid", + "at": [ + "claim", + 2 + ] + }, + { + "name": "input_samples_mismatch", + "about": "an upscale whose evidence names the capture's samples, not those its input decodes to (§4.4)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}", + "{\"class\":\"reconstructed\",\"evidence\":null,\"inputs\":[{\"class\":\"captured\",\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"origin\":\"e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai.neural.int_hyper\",\"oversight\":null,\"record\":null,\"sig\":\"6a098643b18a49fad29ee82395a5d4dfe51a0074fc213947cb09c3010e9ffc8bc02cda5871fdc8dab487390f3ecd50c12c4a06e735e5fc82b1053d1a5f015206\",\"signer_pubkey\":\"ff703397c2545c095bb3a790fde7399961c3076bbefcac542792dc4c00acdd6c\",\"source\":\"digitalCapture\",\"step\":\"convert\",\"subject\":{\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"type\":\"object\"}}", + "{\"class\":\"assistive\",\"evidence\":{\"derivation\":\"wai-synthesis:upscale2\",\"height\":3,\"input_samples\":\"83a041d31be9a9e43824a8cad0d33ff64fc41d0c359ce9414bf2abda7d7b4698\",\"output_samples\":\"9cf4a56cc146a782559d36953010f79ddfbc621a0178460d46ca00693652a2e4\",\"params\":{},\"tier\":\"replicate\",\"width\":4},\"inputs\":[{\"class\":\"reconstructed\",\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"origin\":\"4e09c93646b7a60b0321ca20646718f23ab4ea2712f7608d174544ac9c9a2cfb\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai-synthesis:upscale2\",\"oversight\":null,\"record\":null,\"sig\":\"f8f775eef9e262f0c1843099c157fd153142fc5e2a6bd6d866ef08687d631af9064bb8f1ab71f5f70879b5c56bcedccf0d948177f868e487ced048b74e302804\",\"signer_pubkey\":\"08af3636c89e55c66c0498aacb199aeb6b3e3f3fd25fc4cf42b699a4b86f1070\",\"source\":\"algorithmicallyEnhanced\",\"step\":\"assist\",\"subject\":{\"content_hash\":\"6579d92c519641fbaa6ac9d42e05c3b87f64c4237b32e268cb54d8588773f6e0\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [ + { + "object": "5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2", + "samples": "8d03fc87a587bd2dc32a5d691b153f5dba03c6b445e757f9f960e1e1e7fccfed72f9b16c" + }, + { + "object": "6579d92c519641fbaa6ac9d42e05c3b87f64c4237b32e268cb54d8588773f6e0", + "samples": "8d03fc8a54c287a587a269a5bd2dc37445962a5d692a5d69540c9e635e7172b0459a9065c27184a5659b895ab1895ab11b153f3c68215dba0392b724c6b445d7869fe757f9e757f98a3b909586889fd180b5d16ecbd15cddaa87f084b3f084b3f960e1eda4efe1e7fcd8eab7cfed72e4cf6ff9b16cf9b16cf960e1eda4efe1e7fcd8eab7cfed72e4cf6ff9b16cf9b16c" + }, + { + "object": "bbeddbfbea7af91c2a151e23d18e24223bc5402d73438922f56d00d9cc9f3924", + "samples": "7430b38a6b8f95698f735893754a8a929173b49c7dbf7ba1c171bac4b9a7d4cd87ebac85" + } + ], + "room": null, + "outcome": "refuse", + "reason": "input_samples_mismatch", + "at": [ + "claim", + 2 + ] + }, + { + "name": "assistive_recompute_mismatch", + "about": "an assistive claim whose recomputation differs: the delivered upscale has one sample altered after the derivation, and its evidence states the altered samples (§4.4)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}", + "{\"class\":\"reconstructed\",\"evidence\":null,\"inputs\":[{\"class\":\"captured\",\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"origin\":\"e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai.neural.int_hyper\",\"oversight\":null,\"record\":null,\"sig\":\"6a098643b18a49fad29ee82395a5d4dfe51a0074fc213947cb09c3010e9ffc8bc02cda5871fdc8dab487390f3ecd50c12c4a06e735e5fc82b1053d1a5f015206\",\"signer_pubkey\":\"ff703397c2545c095bb3a790fde7399961c3076bbefcac542792dc4c00acdd6c\",\"source\":\"digitalCapture\",\"step\":\"convert\",\"subject\":{\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"type\":\"object\"}}", + "{\"class\":\"assistive\",\"evidence\":{\"derivation\":\"wai-synthesis:upscale2\",\"height\":3,\"input_samples\":\"6ee5006f705630e934216406a83b76d1115f02ce90970b633f68abede9a5d484\",\"output_samples\":\"abe1854e7bf732fd112b2e422e0b394cb56905aa9da119143c8f1210e35c4355\",\"params\":{},\"tier\":\"replicate\",\"width\":4},\"inputs\":[{\"class\":\"reconstructed\",\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"origin\":\"4e09c93646b7a60b0321ca20646718f23ab4ea2712f7608d174544ac9c9a2cfb\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai-synthesis:upscale2\",\"oversight\":null,\"record\":null,\"sig\":\"bb41dee7d220579d2aac13a53689577f1f92cededc77753af2f53fc5c39f7b2d6df9f4a8ba4edd9c1bce91a37a626eb39ccde18d193eb1ace313febdc528b702\",\"signer_pubkey\":\"08af3636c89e55c66c0498aacb199aeb6b3e3f3fd25fc4cf42b699a4b86f1070\",\"source\":\"algorithmicallyEnhanced\",\"step\":\"assist\",\"subject\":{\"content_hash\":\"6579d92c519641fbaa6ac9d42e05c3b87f64c4237b32e268cb54d8588773f6e0\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [ + { + "object": "5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2", + "samples": "8d03fc87a587bd2dc32a5d691b153f5dba03c6b445e757f9f960e1e1e7fccfed72f9b16c" + }, + { + "object": "6579d92c519641fbaa6ac9d42e05c3b87f64c4237b32e268cb54d8588773f6e0", + "samples": "8d03fc8a54c287e587a269a5bd2dc37445962a5d692a5d69540c9e635e7172b0459a9065c27184a5659b895ab1895ab11b153f3c68215dba0392b724c6b445d7869fe757f9e757f98a3b909586889fd180b5d16ecbd15cddaa87f084b3f084b3f960e1eda4efe1e7fcd8eab7cfed72e4cf6ff9b16cf9b16cf960e1eda4efe1e7fcd8eab7cfed72e4cf6ff9b16cf9b16c" + } + ], + "room": null, + "outcome": "refuse", + "reason": "assistive_recompute_mismatch", + "at": [ + "claim", + 2 + ] + }, + { + "name": "samples_mismatch", + "about": "an upscale whose evidence recomputes, delivered as an object that decodes to other samples (§4.4)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}", + "{\"class\":\"reconstructed\",\"evidence\":null,\"inputs\":[{\"class\":\"captured\",\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"origin\":\"e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai.neural.int_hyper\",\"oversight\":null,\"record\":null,\"sig\":\"6a098643b18a49fad29ee82395a5d4dfe51a0074fc213947cb09c3010e9ffc8bc02cda5871fdc8dab487390f3ecd50c12c4a06e735e5fc82b1053d1a5f015206\",\"signer_pubkey\":\"ff703397c2545c095bb3a790fde7399961c3076bbefcac542792dc4c00acdd6c\",\"source\":\"digitalCapture\",\"step\":\"convert\",\"subject\":{\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"type\":\"object\"}}", + "{\"class\":\"assistive\",\"evidence\":{\"derivation\":\"wai-synthesis:upscale2\",\"height\":3,\"input_samples\":\"6ee5006f705630e934216406a83b76d1115f02ce90970b633f68abede9a5d484\",\"output_samples\":\"9cf4a56cc146a782559d36953010f79ddfbc621a0178460d46ca00693652a2e4\",\"params\":{},\"tier\":\"replicate\",\"width\":4},\"inputs\":[{\"class\":\"reconstructed\",\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"origin\":\"4e09c93646b7a60b0321ca20646718f23ab4ea2712f7608d174544ac9c9a2cfb\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai-synthesis:upscale2\",\"oversight\":null,\"record\":null,\"sig\":\"a0d1198904652fedd4325ec7bf8096bc2684a86e4cec560354c499d5b5628b42ef9a7641f4924ae3ea883ca700dca0f44a2a863024b134574346535899814204\",\"signer_pubkey\":\"08af3636c89e55c66c0498aacb199aeb6b3e3f3fd25fc4cf42b699a4b86f1070\",\"source\":\"algorithmicallyEnhanced\",\"step\":\"assist\",\"subject\":{\"content_hash\":\"6579d92c519641fbaa6ac9d42e05c3b87f64c4237b32e268cb54d8588773f6e0\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [ + { + "object": "5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2", + "samples": "8d03fc87a587bd2dc32a5d691b153f5dba03c6b445e757f9f960e1e1e7fccfed72f9b16c" + }, + { + "object": "6579d92c519641fbaa6ac9d42e05c3b87f64c4237b32e268cb54d8588773f6e0", + "samples": "8d03fc8a54c287e587a269a5bd2dc37445962a5d692a5d69540c9e635e7172b0459a9065c27184a5659b895ab1895ab11b153f3c68215dba0392b724c6b445d7869fe757f9e757f98a3b909586889fd180b5d16ecbd15cddaa87f084b3f084b3f960e1eda4efe1e7fcd8eab7cfed72e4cf6ff9b16cf9b16cf960e1eda4efe1e7fcd8eab7cfed72e4cf6ff9b16cf9b16c" + } + ], + "room": null, + "outcome": "refuse", + "reason": "samples_mismatch", + "at": [ + "claim", + 2 + ] + }, + { + "name": "assistive_not_proved", + "about": "a float super-resolution step with attest evidence, under a policy that requires every assistive claim to be proved (§4.4, §11.1)", + "policy": { + "dual_layer": "when-given", + "assistive": "require-proof" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}", + "{\"class\":\"reconstructed\",\"evidence\":null,\"inputs\":[{\"class\":\"captured\",\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"origin\":\"e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"wai.neural.int_hyper\",\"oversight\":null,\"record\":null,\"sig\":\"6a098643b18a49fad29ee82395a5d4dfe51a0074fc213947cb09c3010e9ffc8bc02cda5871fdc8dab487390f3ecd50c12c4a06e735e5fc82b1053d1a5f015206\",\"signer_pubkey\":\"ff703397c2545c095bb3a790fde7399961c3076bbefcac542792dc4c00acdd6c\",\"source\":\"digitalCapture\",\"step\":\"convert\",\"subject\":{\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"type\":\"object\"}}", + "{\"class\":\"assistive\",\"evidence\":{\"derivation\":\"super-resolution\",\"height\":3,\"input_samples\":\"6ee5006f705630e934216406a83b76d1115f02ce90970b633f68abede9a5d484\",\"output_samples\":\"e9aebdaebbd8ad1ef951b3d2d0cc62272cfcd73f1b69d11363d361826f458b2d\",\"params\":{\"scale\":2},\"tier\":\"attest\",\"width\":4},\"inputs\":[{\"class\":\"reconstructed\",\"content_hash\":\"5c9d10e4264fb166fafb81b5dc92f1e0aff2be288d2242dbd16c466d42c586f2\",\"origin\":\"4e09c93646b7a60b0321ca20646718f23ab4ea2712f7608d174544ac9c9a2cfb\",\"source\":\"digitalCapture\"}],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[{\"digest\":\"e2ea8c07e5e2e2c846c97de6f5f79ab21593563603420e70736ab7f22ca451d4\",\"type\":\"trained\"}],\"operation\":\"super-resolution\",\"oversight\":null,\"record\":null,\"sig\":\"8f3ea73024ea875df6ec23ea0f79b33e760ebb2036b356ad6d2f8813a620b07ddfc27d8e6d2ece330a82db3d5df571c80b3b32880a9bb8e480d12bce3d3e130b\",\"signer_pubkey\":\"08af3636c89e55c66c0498aacb199aeb6b3e3f3fd25fc4cf42b699a4b86f1070\",\"source\":\"algorithmicallyEnhanced\",\"step\":\"assist\",\"subject\":{\"content_hash\":\"d4b33e2b9bad56645fcf0b7856b9009fecd2b4e5e587eeb791a0da87daa601ce\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "assistive_not_proved", + "at": [ + "claim", + 2 + ] + }, + { + "name": "claim_unknown", + "about": "a marking whose origin claim the verifier is not given (§7.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}" + ], + "markings": [ + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-marking\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"payload\":\"03ead74abe77e324\",\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"1c6c99343e99185ae70d5c0d496628cbaa7e00bd50c6539f1a658ab82e663cbe9ea2302b9c125db6005fa5da8eb837f23f843f0e26af975ca63b794c8942610a\",\"signer_pubkey\":\"a56ef30c04f5b691ee3a2f648d5f5036cfed6b7db6514a23122c75e02deccf7c\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\"}" + ], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "claim_unknown", + "at": [ + "marking", + 0 + ] + }, + { + "name": "marking_mismatch", + "about": "a marking whose payload carries the class captured for a synthesized claim (§7.2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [ + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-marking\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"payload\":\"00ead74abe77e324\",\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"9d6ac7d8fa7777a8482722dde6801e7961a7104676de2585aff18895d46621b632001e912ad4fdde125dacf5bb8c9536ad762cd0d41fd9c32eda1c55e2b57205\",\"signer_pubkey\":\"a56ef30c04f5b691ee3a2f648d5f5036cfed6b7db6514a23122c75e02deccf7c\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\"}" + ], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "marking_mismatch", + "at": [ + "marking", + 0 + ] + }, + { + "name": "detection_mismatch", + "about": "a detection under another scheme definition than the one the marking names (§7.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [ + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-marking\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"payload\":\"03ead74abe77e324\",\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"1c6c99343e99185ae70d5c0d496628cbaa7e00bd50c6539f1a658ab82e663cbe9ea2302b9c125db6005fa5da8eb837f23f843f0e26af975ca63b794c8942610a\",\"signer_pubkey\":\"a56ef30c04f5b691ee3a2f648d5f5036cfed6b7db6514a23122c75e02deccf7c\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\"}" + ], + "detections": [ + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-detection\",\"payload\":\"03ead74abe77e324\",\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"5dcbb4c23056dc3f420b43a59f490d5cb1082a48bb276f75977cd52f2e5cafc7\",\"sig\":\"b8e07dee2e8c029a288514a1b3e1cfbe0c8071b3cd3780aa10d41c7d3311efae2a02c9fa63591581b7eaba8faca7bc306d38a3cb5733daaa55ebcf38fac0b800\",\"signer_pubkey\":\"a4ddb8268591a53a9ee6446a9ee8d5917fa7b337a65f806b9d12708cf19322c6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\"}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "detection_mismatch", + "at": [ + "detection", + 0 + ] + }, + { + "name": "watermark_absent", + "about": "a marked object in which the detector finds no mark: the second layer was stripped (§7.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [ + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-marking\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"payload\":\"03ead74abe77e324\",\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"1c6c99343e99185ae70d5c0d496628cbaa7e00bd50c6539f1a658ab82e663cbe9ea2302b9c125db6005fa5da8eb837f23f843f0e26af975ca63b794c8942610a\",\"signer_pubkey\":\"a56ef30c04f5b691ee3a2f648d5f5036cfed6b7db6514a23122c75e02deccf7c\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\"}" + ], + "detections": [ + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-detection\",\"payload\":null,\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"25bf74671a41233c3d263f9789dfc6146c78099f01775ad9026f903524c68ef13eb177de5eac541f4dd48fad7800d14a5f2bc3dd5ea2eb271f43f26ac6fae208\",\"signer_pubkey\":\"a4ddb8268591a53a9ee6446a9ee8d5917fa7b337a65f806b9d12708cf19322c6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\"}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "watermark_absent", + "at": [ + "detection", + 0 + ] + }, + { + "name": "layers_disagree", + "about": "the two marking layers disagree: the photo's claim says captured, and the detector reads from it a synthesized mark that points at another claim (§7.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [ + "{\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-detection\",\"payload\":\"03ead74abe77e324\",\"scheme\":\"wai-mark:conformance\",\"scheme_digest\":\"73ab84afa47d6646e97f18a6bbfebad6f924a6f1ae6f5d78532d727d975acbf3\",\"sig\":\"fe270dbd3df0b210b24468692f42a5a39c5f6038c9b2371c78beb91e7bd636cbd147f6bc6120b3ec417de5f27caea2e8f9f27b43884cf04dec39f55301f7370e\",\"signer_pubkey\":\"a4ddb8268591a53a9ee6446a9ee8d5917fa7b337a65f806b9d12708cf19322c6\",\"subject\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\"}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "layers_disagree", + "at": [ + "detection", + 0 + ] + }, + { + "name": "layer_missing", + "about": "a synthesized object with no marking, under a policy that requires both layers (§7.1)", + "policy": { + "dual_layer": "required", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "layer_missing", + "at": [ + "claim", + 0 + ] + }, + { + "name": "ai_track_unlabelled", + "about": "an AI participant publishes speech with no synthesized track claim for it (§8.2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": { + "descriptor": "5752443101ba07665226a6cc29262167d963816c0949855436567469a282299e19951d15d86d1383a5809797241ba62c20bb2610b653449442b011405f246603c702124cc400406cd8e95d060040420f00000000000000080000000200000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f00000000000204686f73740101000008040000000000000000000000000000000000000000000000000000000000000861756469656e63650001000018000000000000000000000000000000000000000000000000000000000000000001067370656563680101046f707573000001000000000000100000004000000080bb0000010f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008951f673cfa31eaf7f5b68c7755826574bdf858e85925f8ab4ae2122db97b97ce4f0359e91ccefc8caec38d6ce9b2e90d070141e68d23503d93ba41a40c6b904", + "grants": [ + "5752473101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e126e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e74100000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000001010000000000000000000000000000000000000000000000000000000000000000000000574550310108040000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000100011568747470733a2f2f72656c61792e6578616d706c65450002616940003835376631346436326164653437386639306434393561313734393565316161363630383235323230356665316234653062643433646663663134353637363173f388e6e8845b7c383256ed852a5d354251fac217b9aa5cf1f2021719c1dede958544a127652754264fdf2379ff4dabeb0afe0862afc9674b314239a7ccc10a", + "5752473101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e1332dd253be02fa4f9d861626e9e09ad19786534665f684b833f5beaa94075f5400000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010000000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c6500001903d5c5c1f8f10ba284c91c71382d59f316181816ae9a2bb9e232a1fcf7cc40000afe69527553f25c996d3bd3ab0392be40c1baaafbb83ed8b1488207213206" + ], + "key_log": null, + "chains": [ + [ + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e16d1383a5809797241ba62c20bb2610b653449442b011405f246603c702124cc40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010400026e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e7417b98c0c1a0e637327a2d6d5f5fd46e63bb2a5970f6c67586cd7de6f575b6360f92c58afd19b1590b027705e137e7fd9113ec862f4a46f7cf98af22cddf15f6ab0a3d797c19ac7bac710280be04e85315ebb15d4a498b376ba1cf4fa5ef760d0a", + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e16d1383a5809797241ba62c20bb2610b653449442b011405f246603c702124cc40100000000000000e30951a58b352a71167f2653d553a499f42535929d2c70a2df6707d9237813e9000000000000000000104000332dd253be02fa4f9d861626e9e09ad19786534665f684b833f5beaa94075f5469e9ac354d85ed0e33615f6021464551a549219747f1887e8bd0ba8e198a417289544d1a426e53d808c6e39e490a36f4d7bbbb293038f456841cfe524fb294695727a8c1e1261c0274f4268369b32026b31ae73bfadb3f5c1fdc72341ac65604", + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e16d1383a5809797241ba62c20bb2610b653449442b011405f246603c702124cc40200000000000000f01481d18e4abd33b4feb973fad90407428633775ee248a3ee1ba5804452b24740420f00000000000012240026e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e741000100010011eec15a7256a01e340ad71d4ca262cbbd3b5d14b89767610b584fe8a879aaf56e8d567369bc64313dc8857a695c44935af619b0ebb63e9a4fbc1d8847130e" + ], + [ + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e1332dd253be02fa4f9d861626e9e09ad19786534665f684b833f5beaa94075f540000000000000000000000000000000000000000000000000000000000000000000000000000000080841e000000000000030800060048656c6c6f3f53e502ef3d4f09c4a615c8374bc433e727bf8bee9a34083ee5ba47ba2bbe463e96959334a02fd78310e063b5ee409626258cbf429e480c30b85f82349c13890a" + ], + [ + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e126e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e74100000000000000000000000000000000000000000000000000000000000000000000000000000000c0c62d000000000000034600230048656c6c6f2e204920616d20616e206175746f6d6174656420617373697374616e742e02857f14d62ade478f90d495a17495e1aa6608252205fe1b4e0bd43dfcf1456761290bea86e3e37ec17ddaa56e7eb3021ac46aca24e23452fb610549bb3599587822fbc702af39ca05d47e519f6a5bf1aecb37e28b2c784abce201d54026a70105", + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e126e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e741010000000000000052c8b1b099988b7700357fdc8c43b4fb67a8174eab58d664b5490ae5a29ec70200093d0000000000000a3000d007000002857f14d62ade478f90d495a17495e1aa6608252205fe1b4e0bd43dfcf145676102656e060048656c6c6f2ebca90ce1ee985ff5ab8d0c6d4e88fed39c76116454f6ce251b82f9b311cff3eb4440ffd246a25255b26a36529eeae05baab1f489ec4f22cc2cce45ce3bc6d306" + ] + ] + }, + "outcome": "refuse", + "reason": "ai_track_unlabelled", + "at": [ + "room", + "record", + 0, + 2 + ] + }, + { + "name": "ai_unmarked", + "about": "an AI participant's chat that states no origin: the room refuses it (live-rooms §9.2 step 6)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"audio\",\"models\":[{\"digest\":\"857f14d62ade478f90d495a17495e1aa6608252205fe1b4e0bd43dfcf1456761\",\"type\":\"trained\"}],\"operation\":\"speech-synthesis\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"7e7b5b7821bc7928d1e8ae3ae2e25b2149ed3a624a6217db003658b9890afd9373c61d7d4d68e8dceb3664ee077006397b95ee3d2e0171caf73bc66bd6edfc09\",\"signer_pubkey\":\"26e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e741\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"from_group\":0,\"track\":\"e0f5f40db551bfb20f862d5d6ffd64dbea588d2181d8dfe58849b4d9f15931a6\",\"type\":\"track\"}}" + ], + "markings": [], + "detections": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "decoded": [], + "room": { + "descriptor": "5752443101ba07665226a6cc29262167d963816c0949855436567469a282299e19951d15d86d1383a5809797241ba62c20bb2610b653449442b011405f246603c702124cc400406cd8e95d060040420f00000000000000080000000200000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f00000000000204686f73740101000008040000000000000000000000000000000000000000000000000000000000000861756469656e63650001000018000000000000000000000000000000000000000000000000000000000000000001067370656563680101046f707573000001000000000000100000004000000080bb0000010f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008951f673cfa31eaf7f5b68c7755826574bdf858e85925f8ab4ae2122db97b97ce4f0359e91ccefc8caec38d6ce9b2e90d070141e68d23503d93ba41a40c6b904", + "grants": [ + "5752473101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e126e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e74100000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000001010000000000000000000000000000000000000000000000000000000000000000000000574550310108040000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000100011568747470733a2f2f72656c61792e6578616d706c65450002616940003835376631346436326164653437386639306434393561313734393565316161363630383235323230356665316234653062643433646663663134353637363173f388e6e8845b7c383256ed852a5d354251fac217b9aa5cf1f2021719c1dede958544a127652754264fdf2379ff4dabeb0afe0862afc9674b314239a7ccc10a", + "5752473101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e1332dd253be02fa4f9d861626e9e09ad19786534665f684b833f5beaa94075f5400000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010000000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c6500001903d5c5c1f8f10ba284c91c71382d59f316181816ae9a2bb9e232a1fcf7cc40000afe69527553f25c996d3bd3ab0392be40c1baaafbb83ed8b1488207213206" + ], + "key_log": null, + "chains": [ + [ + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e16d1383a5809797241ba62c20bb2610b653449442b011405f246603c702124cc40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010400026e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e7417b98c0c1a0e637327a2d6d5f5fd46e63bb2a5970f6c67586cd7de6f575b6360f92c58afd19b1590b027705e137e7fd9113ec862f4a46f7cf98af22cddf15f6ab0a3d797c19ac7bac710280be04e85315ebb15d4a498b376ba1cf4fa5ef760d0a", + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e16d1383a5809797241ba62c20bb2610b653449442b011405f246603c702124cc40100000000000000e30951a58b352a71167f2653d553a499f42535929d2c70a2df6707d9237813e9000000000000000000104000332dd253be02fa4f9d861626e9e09ad19786534665f684b833f5beaa94075f5469e9ac354d85ed0e33615f6021464551a549219747f1887e8bd0ba8e198a417289544d1a426e53d808c6e39e490a36f4d7bbbb293038f456841cfe524fb294695727a8c1e1261c0274f4268369b32026b31ae73bfadb3f5c1fdc72341ac65604", + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e16d1383a5809797241ba62c20bb2610b653449442b011405f246603c702124cc40200000000000000f01481d18e4abd33b4feb973fad90407428633775ee248a3ee1ba5804452b24740420f00000000000012240026e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e741000100010011eec15a7256a01e340ad71d4ca262cbbd3b5d14b89767610b584fe8a879aaf56e8d567369bc64313dc8857a695c44935af619b0ebb63e9a4fbc1d8847130e" + ], + [ + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e1332dd253be02fa4f9d861626e9e09ad19786534665f684b833f5beaa94075f540000000000000000000000000000000000000000000000000000000000000000000000000000000080841e000000000000030800060048656c6c6f3f53e502ef3d4f09c4a615c8374bc433e727bf8bee9a34083ee5ba47ba2bbe463e96959334a02fd78310e063b5ee409626258cbf429e480c30b85f82349c13890a" + ], + [ + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e126e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e74100000000000000000000000000000000000000000000000000000000000000000000000000000000c0c62d000000000000032500230048656c6c6f2e204920616d20616e206175746f6d6174656420617373697374616e742eed26431d6673851b421ceaa718d614e7dd21643d1a82a6d053883e1e7ae722e0440783d5a2e151115fa23b8a8ae0de570297987d47e192438b2d7c9028900d08", + "5745563101e36c341a23bf13a3594cc864696fb9a89f85cba60faaa0ddf4e776aee2b976e126e40ba726b5d0bd3cba619ecd280967a9f28dff54c01f2c6bc27cd9e0e1e74101000000000000006aec06d5a53a79a3b40ed7be1551ecedcc4aff4ce8eb700b6504bbaca2ad837d00093d0000000000000a3000d007000002857f14d62ade478f90d495a17495e1aa6608252205fe1b4e0bd43dfcf145676102656e060048656c6c6f2e539273e572c829de9e25ec2fa5503cd0240d3e54671d52215fe86fc6aa751c2a2f3aee6f09613ba79e72976b2bbb942b118a1481e689094a7ebb5f21bf59c205" + ] + ] + }, + "outcome": "refuse", + "reason": "ai_unmarked", + "at": [ + "room", + "record", + 2, + 0 + ] + }, + { + "name": "player_chain_broken", + "about": "a player's log whose third entry claims seq 3 (§10.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [ + "{\"decoder\":\"builtin:wai@0.3.0\",\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-review\",\"notes\":null,\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"reconstruction\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"role\":null,\"scope\":\"rendering\",\"sig\":\"a83e554b18fb64e19b394adbd5c866b30ee79de5583e1a083b1187c6c30aa42396ca1a67e755e6cb7f45ce307caa2d9d054903d2c71c59731b32b46dc3629b0b\",\"signer_pubkey\":\"50999f876122d351b1a229a604ec946ddd24838b9e10d2468259405fe4695220\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"variant\":null,\"verdict\":\"approved\"}" + ], + "label_policy": null, + "player": [ + "{\"at\":0,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"first-exposure\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"1071b9e5bc259e948b29c4b6e79c76a4749000d0998f97bb218160faac47a925d8df36ed6fe62a3586c8adae3c3ba32dbc21ad1434bed9c3c00c424b1962950f\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"d43d753b42fa00b30b4bde2820034298f165e27cf9b1b1b8c78e99131e1c8d8a\",\"seq\":1,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"fe343689ded585d0cb6c9b78e508152dd3f537125c2634bdc0c53b53402d7db14af0aac5431c8a24059d5f005d8d501f1cd68d1ff2bfa62fde8858a945506f01\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":1000000,\"entry\":{\"review\":\"f53388638eec4d3c50ad26873a266d51a5468f2b9df922e30d93e3980fa60282\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"review\"},\"kind\":\"wai-player\",\"prev\":\"780d0b5b9cb2a9439625ed2ae4cd88fa58078cd8faf9a93ce698f415467a79a3\",\"seq\":3,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"138a9392b83fb6ee462d2ef1da462ebbd86c239e50aa9ceaeca943aaf91aee47cc6a8acb9829cf154b77502351dc6e4e41e4debda6c56d3712b2e41e8155e406\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20000000,\"entry\":{\"after\":\"ad-break\",\"type\":\"resume\"},\"kind\":\"wai-player\",\"prev\":\"8798772ab497744055c606abf9512a977710114b1d2e823fe8feb41e87ff94b1\",\"seq\":3,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"07605ee164d8b229cc90d97cee85ae4f16024b2ebd8d4ed36a1b2cd71f3c6b0dbbed9c2bbe2aecd99047c0f9bce48dbcf22fab06121382c3bfbd750400b89b09\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20200000,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"resume\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":\"e74865cf7556384fb5f978b1c89baa512762d79a4dda6e5fd3de460f211a46be\",\"seq\":4,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"082fb36c2f63a351bd2e6a5a1d697f300cda5a33405d08bf42a9d79f2c1e2579735d4ab1dd46cb1e2d51c25ab601135117df90ebdc34d61516cbcabf4798d802\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20200000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"0ce999210725d04111c829732c363e5587c1a13eb6990a74bf6ab09b3e4faa8f\",\"seq\":5,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"66ba5826145a6d7a17ed89e8bf8faa725bb34942afb3e86046a4d7380c57f8e7fec3bb6d3e55d01e76927065f13536b86a061eec0ba776de732462e61ed6940d\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":40000000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"4c772443e1c5b98c79ff193646ec9ee9ef00062f0bb8ab89330d08088e281686\",\"seq\":6,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"4429d3e146c5f24ad739037848fe8531a5978b062632351c231b5a827e9463b4390143db6d7a4acfe9b2f0234dd4826baa60930422ab2af65746d34716ca1a0f\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}" + ], + "objects": [ + "57414931660000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617631227d7d400000001c8af660db156fd4777ceafd936d90bf46e4ea9d79272f80bbcfdce23a6da360aa0772307063ca56ee4d1d2b75d5de565c3895a9c230ba3facfad6d1579caf53" + ], + "decoded": [ + { + "object": "66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5", + "samples": "c5ab2471427ff3fc018ffd5106fede9fa4155895fffa97099b03775d4b1a277cfb628b04" + } + ], + "room": null, + "outcome": "refuse", + "reason": "player_chain_broken", + "at": [ + "player", + 2 + ] + }, + { + "name": "present_mismatch", + "about": "a presentation that states the class assistive for a synthesized object (§10.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [ + "{\"decoder\":\"builtin:wai@0.3.0\",\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-review\",\"notes\":null,\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"reconstruction\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"role\":null,\"scope\":\"rendering\",\"sig\":\"a83e554b18fb64e19b394adbd5c866b30ee79de5583e1a083b1187c6c30aa42396ca1a67e755e6cb7f45ce307caa2d9d054903d2c71c59731b32b46dc3629b0b\",\"signer_pubkey\":\"50999f876122d351b1a229a604ec946ddd24838b9e10d2468259405fe4695220\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"variant\":null,\"verdict\":\"approved\"}" + ], + "label_policy": null, + "player": [ + "{\"at\":0,\"entry\":{\"class\":\"assistive\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"1ea1852b12e1a16e3d00536ee2f72f55aaf4fe3e22502382580417551ce70132c7b1885ca9de36147547cac754b446418dedb248803944916d1a3b21db8ad106\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}" + ], + "objects": [ + "57414931660000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617631227d7d400000001c8af660db156fd4777ceafd936d90bf46e4ea9d79272f80bbcfdce23a6da360aa0772307063ca56ee4d1d2b75d5de565c3895a9c230ba3facfad6d1579caf53" + ], + "decoded": [ + { + "object": "66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5", + "samples": "c5ab2471427ff3fc018ffd5106fede9fa4155895fffa97099b03775d4b1a277cfb628b04" + } + ], + "room": null, + "outcome": "refuse", + "reason": "present_mismatch", + "at": [ + "player", + 0 + ] + }, + { + "name": "present_output_irreproducible", + "about": "a presentation whose stated output is not what the object decodes to (§9.2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [ + "{\"decoder\":\"builtin:wai@0.3.0\",\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-review\",\"notes\":null,\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"reconstruction\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"role\":null,\"scope\":\"rendering\",\"sig\":\"a83e554b18fb64e19b394adbd5c866b30ee79de5583e1a083b1187c6c30aa42396ca1a67e755e6cb7f45ce307caa2d9d054903d2c71c59731b32b46dc3629b0b\",\"signer_pubkey\":\"50999f876122d351b1a229a604ec946ddd24838b9e10d2468259405fe4695220\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"variant\":null,\"verdict\":\"approved\"}" + ], + "label_policy": null, + "player": [ + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"3b5fba5eb3db540dac0c9a53ed9b9d5baea76983b6c0ea792ec245123afea97f\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"82460c3e5fc6dc63ba05f2a9304396a6086e0d15f08e05f73f35186f9ce6023ce602506fd22935e7fa281a0f7339620959445100879fd27ef271387c91874f09\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}" + ], + "objects": [ + "57414931660000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617631227d7d400000001c8af660db156fd4777ceafd936d90bf46e4ea9d79272f80bbcfdce23a6da360aa0772307063ca56ee4d1d2b75d5de565c3895a9c230ba3facfad6d1579caf53" + ], + "decoded": [ + { + "object": "66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5", + "samples": "c5ab2471427ff3fc018ffd5106fede9fa4155895fffa97099b03775d4b1a277cfb628b04" + } + ], + "room": null, + "outcome": "refuse", + "reason": "present_output_irreproducible", + "at": [ + "player", + 0 + ] + }, + { + "name": "review_subject_mismatch", + "about": "a review presented for an object the player has not presented (§9.2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}", + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"image\",\"models\":[],\"operation\":\"capture\",\"oversight\":null,\"record\":null,\"sig\":\"80a29a68d5c2e94c740854784f5b7888baf3455414fb7972165fa420d2270c41cc98f1b6fdef1d9d742d335da9d9cc667d9389c938e47af7a7ea62cb1e4e9203\",\"signer_pubkey\":\"32a9d511e269b29d32c9a1e075f2711ab1ac857448fd64631c852b2abf5a3199\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [ + "{\"decoder\":\"builtin:wai@0.3.0\",\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-review\",\"notes\":null,\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"reconstruction\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"role\":null,\"scope\":\"rendering\",\"sig\":\"a83e554b18fb64e19b394adbd5c866b30ee79de5583e1a083b1187c6c30aa42396ca1a67e755e6cb7f45ce307caa2d9d054903d2c71c59731b32b46dc3629b0b\",\"signer_pubkey\":\"50999f876122d351b1a229a604ec946ddd24838b9e10d2468259405fe4695220\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"variant\":null,\"verdict\":\"approved\"}" + ], + "label_policy": null, + "player": [ + "{\"at\":0,\"entry\":{\"class\":\"captured\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"image\",\"origin\":\"e48406173ab889163527e5f734bd3c02ac1f47d8c30538903b9326fd886ffe31\",\"output\":null,\"subject\":\"69c268ffc3b739ef3cddf708a9e9ffcdb360ead5e61f9d3e544b7cef55a7b681\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"4db091b756ed0adce9280a4a98e83ac6c7c03e593d5c30100753b4cbcc8c51ad833952bbf241b826a10a00161b160cb9f7f0590377afd87eaf3c1b7c5bb52b09\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":1000000,\"entry\":{\"review\":\"f53388638eec4d3c50ad26873a266d51a5468f2b9df922e30d93e3980fa60282\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"review\"},\"kind\":\"wai-player\",\"prev\":\"f08067bc424759141b9707e3c29a0244f0cf979fb3e4287662d8bce4254007cc\",\"seq\":1,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"8546cd13e3241c2f05e6b86a0c1d428c131fe92aae4c510ccddb60fc069416478d58672311f4ddebb05e30a690b0b44a4b33bf2382c60d2e4deed6ba60b51e06\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}" + ], + "objects": [], + "decoded": [], + "room": null, + "outcome": "refuse", + "reason": "review_subject_mismatch", + "at": [ + "player", + 1 + ] + }, + { + "name": "review_rejected", + "about": "a rejected review presented as a review (§9.2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [ + "{\"decoder\":\"builtin:wai@0.3.0\",\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-review\",\"notes\":\"2289937744cfc8400150b9fc1e54ff8483a8cc133dbefeb7c8585abba617c656\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"reconstruction\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"role\":null,\"scope\":\"rendering\",\"sig\":\"64533ec6c26fa5d923e12a5ec3821ac2c7a2ebb0285ada93726fa61f8998fe0e0743428eeeca0e3dc00eb1518094da9303c2210265c19b504d13713c58141a0d\",\"signer_pubkey\":\"50999f876122d351b1a229a604ec946ddd24838b9e10d2468259405fe4695220\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"variant\":null,\"verdict\":\"rejected\"}" + ], + "label_policy": null, + "player": [ + "{\"at\":0,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"first-exposure\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"1071b9e5bc259e948b29c4b6e79c76a4749000d0998f97bb218160faac47a925d8df36ed6fe62a3586c8adae3c3ba32dbc21ad1434bed9c3c00c424b1962950f\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"d43d753b42fa00b30b4bde2820034298f165e27cf9b1b1b8c78e99131e1c8d8a\",\"seq\":1,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"fe343689ded585d0cb6c9b78e508152dd3f537125c2634bdc0c53b53402d7db14af0aac5431c8a24059d5f005d8d501f1cd68d1ff2bfa62fde8858a945506f01\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":1000000,\"entry\":{\"review\":\"caf32843208613369c28061e2359d8d32c21b3dbb35a0002bcc23e4aecde4976\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"review\"},\"kind\":\"wai-player\",\"prev\":\"780d0b5b9cb2a9439625ed2ae4cd88fa58078cd8faf9a93ce698f415467a79a3\",\"seq\":2,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"49037448b7380753ee0eac6872f296d3b7c117d726c9dbb6794ed9e6f1ba375a77a2eba06aca78ec1c7f308899430da6ab856a7da272e5736125cc350a134d03\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20000000,\"entry\":{\"after\":\"ad-break\",\"type\":\"resume\"},\"kind\":\"wai-player\",\"prev\":\"836d89d5b7c71735f05d384c7b3601f83c108dc197de88efe1b0814d2a82b2b8\",\"seq\":3,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"b8d7e997906e501b27f9c57f55eac8adddae05ef116537b29c67f52bb34ad8088e93156a90707b0f7630f064f71f2fa3f7f358c80b388658ed64f7fb4edd8504\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20200000,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"resume\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":\"9ffd5c79482c4bd2628191f4007cc6bba08b50f9e6d5f2e53e57732c26f58331\",\"seq\":4,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"96b21455f13f9fd901b684c6b1e458d3f6b545219d768340b1b14ec2cbb2538aed32e55f1f224d07d9506705d200e9e85a191da9dc3d7dda85a63ccc6e10f008\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20200000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"433c3da0885e5baab9892de4af5213ae8168b23b9f6f5e115f046e7543f01a3c\",\"seq\":5,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"7b55ff96c4cfbe269ab443e371c2e2b32cad19679066cc2723cb2dc304ec6c162323d74be5aafadcff31aa94925458580ce98aecb5fc30a1598d831b525c5a0b\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":40000000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"3e41d12949a681e2f73bd37149308f46df40f01e38da3edd2a00eab0e21b7532\",\"seq\":6,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"d4b024dde23095aec2e9655c7fafdb5519cc8e3ab1e42f856bf84eacc70538bc94f80e8ca692ce4c6d4420fb9d448901ae0a6cb816ec5eac6628373c7301ad01\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}" + ], + "objects": [ + "57414931660000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617631227d7d400000001c8af660db156fd4777ceafd936d90bf46e4ea9d79272f80bbcfdce23a6da360aa0772307063ca56ee4d1d2b75d5de565c3895a9c230ba3facfad6d1579caf53" + ], + "decoded": [ + { + "object": "66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5", + "samples": "c5ab2471427ff3fc018ffd5106fede9fa4155895fffa97099b03775d4b1a277cfb628b04" + } + ], + "room": null, + "outcome": "refuse", + "reason": "review_rejected", + "at": [ + "player", + 2 + ] + }, + { + "name": "review_path_not_exact", + "about": "a review presented on a non-exact path: the player decoded through a float path, tier none, and presents a review made at decode-equivalence (§9.2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [ + "{\"decoder\":\"builtin:wai@0.3.0\",\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-review\",\"notes\":null,\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"reconstruction\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"role\":null,\"scope\":\"rendering\",\"sig\":\"a83e554b18fb64e19b394adbd5c866b30ee79de5583e1a083b1187c6c30aa42396ca1a67e755e6cb7f45ce307caa2d9d054903d2c71c59731b32b46dc3629b0b\",\"signer_pubkey\":\"50999f876122d351b1a229a604ec946ddd24838b9e10d2468259405fe4695220\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"variant\":null,\"verdict\":\"approved\"}" + ], + "label_policy": null, + "player": [ + "{\"at\":0,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"first-exposure\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"1071b9e5bc259e948b29c4b6e79c76a4749000d0998f97bb218160faac47a925d8df36ed6fe62a3586c8adae3c3ba32dbc21ad1434bed9c3c00c424b1962950f\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"none\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"d43d753b42fa00b30b4bde2820034298f165e27cf9b1b1b8c78e99131e1c8d8a\",\"seq\":1,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"a0f9c824a92d705550daa996995cd11fed4a3726d32423561f035a61b0e52cd5b1d392943bed269a619c8a723a50b508b8e66882cf3ffb25d18e5fc373de5b0c\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":1000000,\"entry\":{\"review\":\"f53388638eec4d3c50ad26873a266d51a5468f2b9df922e30d93e3980fa60282\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"review\"},\"kind\":\"wai-player\",\"prev\":\"42cd88a2a3ae83a7d6274583661a01ca6e232b18c5d00cf32c062e25c08066fb\",\"seq\":2,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"8c27e4ad655a0424adf11c8e3f5a0143d78ee96240744a7cfa3d70109abb03d26ca3500bb59239c8a0763cac6ee4e5984aa02a306c0458b9071c0835e087c30a\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20000000,\"entry\":{\"after\":\"ad-break\",\"type\":\"resume\"},\"kind\":\"wai-player\",\"prev\":\"c4cf7acaab920e31e97e905ce5e7a4728379fcd45f941c7b2b419c937406bfb7\",\"seq\":3,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"40beb6601e0937f48eba68205f32c63a55b863cf727a3ac9ae42a46c5079edeb53348addb9b52b94b22eb8596478f9618de753c56fe706dde6ccfa731213a606\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20200000,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"resume\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":\"59dfd3011bc41eac69093c898f1741cd961cff83a187d1ebee92390ee43999ba\",\"seq\":4,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"b558dfd15d3ead0f31a89d31f6786055709921a1153a7ec67255b9ec2fa6afcd03e774f02c31ee9dbfe73ea1793eed1355c97ed8a0be549ea1120c6d09c7a208\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20200000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"none\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"b7e80bf47b7284974e1c981f275f4b3e09380762dd4e5b66945514a80a1cfcc5\",\"seq\":5,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"a01507cd82477d0ed71f10f503882d2f292dec099a794c6415d17b05c10669a15b6da91fce1e3f3d2ec822f261bef95d536675392f8a2b331bcee48ef37f630b\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":40000000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"none\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"3b8adf722ceebe95bc880e727d85c3df6961afc32444f4597bb9f4cfcd03d46e\",\"seq\":6,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"6f0b6f701704e0db869f971a7ae2d2ced80043e48cded58e60cca80390604bf093b1b0efaab8136a840503911da46b8e0a662a0233cb1633e40b35a613a41807\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}" + ], + "objects": [ + "57414931660000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617631227d7d400000001c8af660db156fd4777ceafd936d90bf46e4ea9d79272f80bbcfdce23a6da360aa0772307063ca56ee4d1d2b75d5de565c3895a9c230ba3facfad6d1579caf53" + ], + "decoded": [ + { + "object": "66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5", + "samples": "c5ab2471427ff3fc018ffd5106fede9fa4155895fffa97099b03775d4b1a277cfb628b04" + } + ], + "room": null, + "outcome": "refuse", + "reason": "review_path_not_exact", + "at": [ + "player", + 2 + ] + }, + { + "name": "review_reconstruction_mismatch", + "about": "a review over another reconstruction than the one the player presented (§9.2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [ + "{\"decoder\":\"builtin:wai@0.3.0\",\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-review\",\"notes\":null,\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"reconstruction\":\"ba04f104eaca15e623d77e6f76a4d6f25bf34ba874689075f2f4355acba117b9\",\"role\":null,\"scope\":\"rendering\",\"sig\":\"776c309c7f12edcd1ecb648a64ea6ea925c7bc98380b3977894a20fbccaadc1ed10c13ba78ef193cc64d3aeb3b957bbd4a7723971f213fc1b6d35dbbe7ac9602\",\"signer_pubkey\":\"50999f876122d351b1a229a604ec946ddd24838b9e10d2468259405fe4695220\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"variant\":null,\"verdict\":\"approved\"}" + ], + "label_policy": null, + "player": [ + "{\"at\":0,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"first-exposure\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"1071b9e5bc259e948b29c4b6e79c76a4749000d0998f97bb218160faac47a925d8df36ed6fe62a3586c8adae3c3ba32dbc21ad1434bed9c3c00c424b1962950f\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"d43d753b42fa00b30b4bde2820034298f165e27cf9b1b1b8c78e99131e1c8d8a\",\"seq\":1,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"fe343689ded585d0cb6c9b78e508152dd3f537125c2634bdc0c53b53402d7db14af0aac5431c8a24059d5f005d8d501f1cd68d1ff2bfa62fde8858a945506f01\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":1000000,\"entry\":{\"review\":\"f75c31a84eed9a61e63ef9fc0c755d55ab4e4a484e1a15ad2cc784598c8e7d9a\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"review\"},\"kind\":\"wai-player\",\"prev\":\"780d0b5b9cb2a9439625ed2ae4cd88fa58078cd8faf9a93ce698f415467a79a3\",\"seq\":2,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"6b1fdf461b3ad2b164bbdebbc4c651d93f5993d3a02da5f261ec809ac0eab74626725f865700a7d00a48419cf6ead1962ea428b3564f5041992625c4bd351f06\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20000000,\"entry\":{\"after\":\"ad-break\",\"type\":\"resume\"},\"kind\":\"wai-player\",\"prev\":\"6d050a1d6957f9e5130b9ab42b117ae41730a8ee11132077fb95dc6f638dafc9\",\"seq\":3,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"550c2359ee27b9f77659a9a46ed1dfa48d8cc2cae6c6579c9ea305f5a76ff828c6e205ac625f949b846ab2a1bbf3fc49ea0e25e02bd3f1832c591e99e9b7570b\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20200000,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"resume\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":\"66a619e4026deab8f013042576712be47facdd1dae417d8e939c5d2b3f362458\",\"seq\":4,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"8c54f3f3dde8c3cc417cbd72b57951939a731773397c70a92a4d0eb722260dd541dca554f8b4778ab3eb9c3ab8e72dd7b10e45254c7fa88f4a0a33f33e0a3a02\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20200000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"cab9bdbdd7830643a070d2282ce445b01415ed63418a077862cf59e869b6dbd6\",\"seq\":5,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"65303588a64ee603c98d665ac2322188453013bc34ad662c7a6a3866cd9356049e7dc2822c3570a8fe950008cab92910d7096c3829fd0ec390a38b37b4293d00\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":40000000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"ed8724dd2a9baa3d91d4337ca476edc5eab15c9905965c857e4a8b64e8da2852\",\"seq\":6,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"ef9e7426734ba4d8427648ebf86c3f1d5d1ba1b69ebf29fd18ba86c917558db0f86a4a128c54819bf145e68b90b05719c65cbba4a371dcc5025ff7cff58cc000\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}" + ], + "objects": [ + "57414931660000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617631227d7d400000001c8af660db156fd4777ceafd936d90bf46e4ea9d79272f80bbcfdce23a6da360aa0772307063ca56ee4d1d2b75d5de565c3895a9c230ba3facfad6d1579caf53" + ], + "decoded": [ + { + "object": "66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5", + "samples": "c5ab2471427ff3fc018ffd5106fede9fa4155895fffa97099b03775d4b1a277cfb628b04" + } + ], + "room": null, + "outcome": "refuse", + "reason": "review_reconstruction_mismatch", + "at": [ + "player", + 2 + ] + }, + { + "name": "label_asset_mismatch", + "about": "a label shown with another icon than the policy's (§10.4)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [ + "{\"decoder\":\"builtin:wai@0.3.0\",\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-review\",\"notes\":null,\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"reconstruction\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"role\":null,\"scope\":\"rendering\",\"sig\":\"a83e554b18fb64e19b394adbd5c866b30ee79de5583e1a083b1187c6c30aa42396ca1a67e755e6cb7f45ce307caa2d9d054903d2c71c59731b32b46dc3629b0b\",\"signer_pubkey\":\"50999f876122d351b1a229a604ec946ddd24838b9e10d2468259405fe4695220\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"variant\":null,\"verdict\":\"approved\"}" + ], + "label_policy": "{\"audio\":\"6e0d3d5e9f9661b8c2327e57ebe1b360b204e109def81020a485edb8bf2f58e8\",\"classes\":[\"synthesized\"],\"grace_us\":500000,\"interval_us\":null,\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-label-policy\",\"sig\":\"b61490b72f87faabcfd2e5d34b3ec3093b0d7e5378657eab2d8a24eec7238bf846ac7093e9fc25ab85a8544ac0247b9d672c71e05823f14afa1c58b794ef2702\",\"signer_pubkey\":\"34e2cdfcf44eaae9f3974cd801473ae99e6779b7e3d432fc12ff35f26d429948\",\"visual\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\"}", + "player": [ + "{\"at\":0,\"entry\":{\"asset\":\"2e7349e5eca4ee78eeef160dfef4545c31850932245373a07c5401e73e6599c0\",\"label\":\"visual\",\"trigger\":\"first-exposure\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"ca56cbca830539174672ec41f3211e18e5c52dc7b9790bd1a379bf0032b3ae5b0b7c72f459a3c550bb5f9d4d38eeb20a1ce2c9a1392bd1f79657bc510ad5fe0e\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"765545f6441d7daec49eb3dbbd918721bf7de95db6cf6fca2733f49b7f0e4690\",\"seq\":1,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"88c3b89e4afedca57ab3855fed7cd06d11b8cb99eb028d6f5270ede51514209bdfdb405b8c8b47afb346618cdd20c90f1bb5df9cf3f7827a583da1ac404b1f02\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}" + ], + "objects": [ + "57414931660000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617631227d7d400000001c8af660db156fd4777ceafd936d90bf46e4ea9d79272f80bbcfdce23a6da360aa0772307063ca56ee4d1d2b75d5de565c3895a9c230ba3facfad6d1579caf53" + ], + "decoded": [ + { + "object": "66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5", + "samples": "c5ab2471427ff3fc018ffd5106fede9fa4155895fffa97099b03775d4b1a277cfb628b04" + } + ], + "room": null, + "outcome": "refuse", + "reason": "label_asset_mismatch", + "at": [ + "player", + 0 + ] + }, + { + "name": "label_not_shown", + "about": "a label not shown at first exposure: the player's log shows the generated video presented at 0 s and the label first at 5 s, past the half-second grace (§10.4)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [ + "{\"decoder\":\"builtin:wai@0.3.0\",\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-review\",\"notes\":null,\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"reconstruction\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"role\":null,\"scope\":\"rendering\",\"sig\":\"a83e554b18fb64e19b394adbd5c866b30ee79de5583e1a083b1187c6c30aa42396ca1a67e755e6cb7f45ce307caa2d9d054903d2c71c59731b32b46dc3629b0b\",\"signer_pubkey\":\"50999f876122d351b1a229a604ec946ddd24838b9e10d2468259405fe4695220\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"variant\":null,\"verdict\":\"approved\"}" + ], + "label_policy": "{\"audio\":\"6e0d3d5e9f9661b8c2327e57ebe1b360b204e109def81020a485edb8bf2f58e8\",\"classes\":[\"synthesized\"],\"grace_us\":500000,\"interval_us\":null,\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-label-policy\",\"sig\":\"b61490b72f87faabcfd2e5d34b3ec3093b0d7e5378657eab2d8a24eec7238bf846ac7093e9fc25ab85a8544ac0247b9d672c71e05823f14afa1c58b794ef2702\",\"signer_pubkey\":\"34e2cdfcf44eaae9f3974cd801473ae99e6779b7e3d432fc12ff35f26d429948\",\"visual\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\"}", + "player": [ + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"f21bba0e5692e924276851e11a2b25c5c6f89a04bd2203ee6b3ad9bb05fe7730a719f898349e508cac73cb0dfa00e92d448a332d7089e91b8f930ac7dcfd740f\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":5000000,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"first-exposure\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":\"fb96c762c013d781cea7e1a4b0039d4e9d08589e598dbd495a7b97c2cb42391f\",\"seq\":1,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"cd1c449189286f52e170c01a3a140d62e88874976fbcba205d8fc889ec1971de3a12cf1ad62330fcd73e135dd1c736def497d3c9ab5feb832c31a2feb3f6af0e\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}" + ], + "objects": [ + "57414931660000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617631227d7d400000001c8af660db156fd4777ceafd936d90bf46e4ea9d79272f80bbcfdce23a6da360aa0772307063ca56ee4d1d2b75d5de565c3895a9c230ba3facfad6d1579caf53" + ], + "decoded": [ + { + "object": "66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5", + "samples": "c5ab2471427ff3fc018ffd5106fede9fa4155895fffa97099b03775d4b1a277cfb628b04" + } + ], + "room": null, + "outcome": "refuse", + "reason": "label_not_shown", + "at": [ + "player", + 0 + ] + }, + { + "name": "label_not_shown_resume", + "about": "no label after an ad break: presentation resumes at 20 s with no label shown since (§10.4)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [ + "{\"decoder\":\"builtin:wai@0.3.0\",\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-review\",\"notes\":null,\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"reconstruction\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"role\":null,\"scope\":\"rendering\",\"sig\":\"a83e554b18fb64e19b394adbd5c866b30ee79de5583e1a083b1187c6c30aa42396ca1a67e755e6cb7f45ce307caa2d9d054903d2c71c59731b32b46dc3629b0b\",\"signer_pubkey\":\"50999f876122d351b1a229a604ec946ddd24838b9e10d2468259405fe4695220\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"variant\":null,\"verdict\":\"approved\"}" + ], + "label_policy": "{\"audio\":\"6e0d3d5e9f9661b8c2327e57ebe1b360b204e109def81020a485edb8bf2f58e8\",\"classes\":[\"synthesized\"],\"grace_us\":500000,\"interval_us\":null,\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-label-policy\",\"sig\":\"b61490b72f87faabcfd2e5d34b3ec3093b0d7e5378657eab2d8a24eec7238bf846ac7093e9fc25ab85a8544ac0247b9d672c71e05823f14afa1c58b794ef2702\",\"signer_pubkey\":\"34e2cdfcf44eaae9f3974cd801473ae99e6779b7e3d432fc12ff35f26d429948\",\"visual\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\"}", + "player": [ + "{\"at\":0,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"first-exposure\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"1071b9e5bc259e948b29c4b6e79c76a4749000d0998f97bb218160faac47a925d8df36ed6fe62a3586c8adae3c3ba32dbc21ad1434bed9c3c00c424b1962950f\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"d43d753b42fa00b30b4bde2820034298f165e27cf9b1b1b8c78e99131e1c8d8a\",\"seq\":1,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"fe343689ded585d0cb6c9b78e508152dd3f537125c2634bdc0c53b53402d7db14af0aac5431c8a24059d5f005d8d501f1cd68d1ff2bfa62fde8858a945506f01\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20000000,\"entry\":{\"after\":\"ad-break\",\"type\":\"resume\"},\"kind\":\"wai-player\",\"prev\":\"780d0b5b9cb2a9439625ed2ae4cd88fa58078cd8faf9a93ce698f415467a79a3\",\"seq\":2,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"6079f3cea97c7c11afa22040ff77834432ba3b4cee60d3151005e76eec1fdedbfb3ece516967912b70157b11509bb6e70f786b0be2427e037369cf423cc52002\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":20000000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"a2577eca3523191dabfefae089c7d147d27dbccbda1154097d4b7afa6ba7358e\",\"seq\":3,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"f9cea1860d28fdaf58c436674c7f84b70ebb19b41dd3a3151fdcab8a48cdbd0834205cea6d541aecff08225cb92f179f59df8468af84d7360a52cf90e2bc710c\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}" + ], + "objects": [ + "57414931660000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617631227d7d400000001c8af660db156fd4777ceafd936d90bf46e4ea9d79272f80bbcfdce23a6da360aa0772307063ca56ee4d1d2b75d5de565c3895a9c230ba3facfad6d1579caf53" + ], + "decoded": [ + { + "object": "66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5", + "samples": "c5ab2471427ff3fc018ffd5106fede9fa4155895fffa97099b03775d4b1a277cfb628b04" + } + ], + "room": null, + "outcome": "refuse", + "reason": "label_not_shown", + "at": [ + "player", + 3 + ] + }, + { + "name": "label_not_shown_interval", + "about": "no label within the policy's 30 s interval: the label last shown at 0 s, the video still presented at 45 s (§10.4)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted" + }, + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1f6d80520cd377fd820fa6727c88d5ef63bb3d4e88592c4915198d92f9d8fec0\",\"type\":\"trained\"}],\"operation\":\"video-generation\",\"oversight\":\"fully_autonomous\",\"record\":null,\"sig\":\"ca9048fd3de7f91e9d7942ca66b2416a447448a37c8008e883aba0ea8b12612cb54c404d1ef63e613d10cfe8753712420756e121e0567790d26cc93bd616d90a\",\"signer_pubkey\":\"05e74a13f7535dd2908c8fdd5b5b0fe6e28b49364593a60aca4461fda5c335ca\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"type\":\"object\"}}" + ], + "markings": [], + "detections": [], + "reviews": [ + "{\"decoder\":\"builtin:wai@0.3.0\",\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-review\",\"notes\":null,\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"reconstruction\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"role\":null,\"scope\":\"rendering\",\"sig\":\"a83e554b18fb64e19b394adbd5c866b30ee79de5583e1a083b1187c6c30aa42396ca1a67e755e6cb7f45ce307caa2d9d054903d2c71c59731b32b46dc3629b0b\",\"signer_pubkey\":\"50999f876122d351b1a229a604ec946ddd24838b9e10d2468259405fe4695220\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"variant\":null,\"verdict\":\"approved\"}" + ], + "label_policy": "{\"audio\":\"6e0d3d5e9f9661b8c2327e57ebe1b360b204e109def81020a485edb8bf2f58e8\",\"classes\":[\"synthesized\"],\"grace_us\":500000,\"interval_us\":30000000,\"issued_at\":\"2026-10-21T09:00:00Z\",\"kind\":\"wai-label-policy\",\"sig\":\"50693722b2490992604330c305b695ac8ce99b2e4ac291dadbedcc9a0d9e128f965ee347a360675481d441f67145b5dd22417345274b9b337953ebb23479d300\",\"signer_pubkey\":\"34e2cdfcf44eaae9f3974cd801473ae99e6779b7e3d432fc12ff35f26d429948\",\"visual\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\"}", + "player": [ + "{\"at\":0,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"first-exposure\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"1071b9e5bc259e948b29c4b6e79c76a4749000d0998f97bb218160faac47a925d8df36ed6fe62a3586c8adae3c3ba32dbc21ad1434bed9c3c00c424b1962950f\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"d43d753b42fa00b30b4bde2820034298f165e27cf9b1b1b8c78e99131e1c8d8a\",\"seq\":1,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"fe343689ded585d0cb6c9b78e508152dd3f537125c2634bdc0c53b53402d7db14af0aac5431c8a24059d5f005d8d501f1cd68d1ff2bfa62fde8858a945506f01\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}", + "{\"at\":45000000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"builtin:wai@0.3.0\",\"media\":\"video\",\"origin\":\"ead74abe77e32490a3e184e17b4a7e8296c102dba77118b02fbebd20b75cd3f0\",\"output\":\"5ac2b64f4b8208dda9cd224d92353fd74b917d3ada6c08c65dfe95712ce32ef6\",\"subject\":\"66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5\",\"tier\":\"decode-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"780d0b5b9cb2a9439625ed2ae4cd88fa58078cd8faf9a93ce698f415467a79a3\",\"seq\":2,\"session\":\"05592ceaa80207a5dd1d9e8e7d5c99498f383eccd17c771a52ecc0a3f5f0f7cd\",\"sig\":\"cb4d6410218850372793539a29ea2b0a311ef813d6284f9fc172e4dba10b94c7bb95e3397cf491ffe4daa4845dd634d6cad53072dd24cc43dd8dcd043666da00\",\"signer_pubkey\":\"008980373ce127480e76dfdc937a2c8f61014c328d905c99c47becbd89ba1c5f\"}" + ], + "objects": [ + "57414931660000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227065726365707475616c222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617631227d7d400000001c8af660db156fd4777ceafd936d90bf46e4ea9d79272f80bbcfdce23a6da360aa0772307063ca56ee4d1d2b75d5de565c3895a9c230ba3facfad6d1579caf53" + ], + "decoded": [ + { + "object": "66aef0dce547f18873c4d53fdbf1c23231f0ea7f438be6e340037bda899073f5", + "samples": "c5ab2471427ff3fc018ffd5106fede9fa4155895fffa97099b03775d4b1a277cfb628b04" + } + ], + "room": null, + "outcome": "refuse", + "reason": "label_not_shown", + "at": [ + "player", + 2 + ] + } + ] +} diff --git a/wai/live-conformance/synthesis_verify.py b/wai/live-conformance/synthesis_verify.py new file mode 100644 index 000000000..e37260673 --- /dev/null +++ b/wai/live-conformance/synthesis_verify.py @@ -0,0 +1,864 @@ +#!/usr/bin/env python3 +"""Second implementation of extensions/synthesis.md (`wai.media.synthesis`), over the corpus +`live-conformance/synthesis/`. + +Written from the extension's text. It shares no code with wai-rs: it reads every record as §2 +has it read (strict JSON, the encodings of jwp-receipts §2.4.3), verifies every signature with +WAI's strict Ed25519 rule (claim-conformance/_ed25519.py) and every link hash with BLAKE3 +written from its specification (claim-conformance/_blake3.py), runs the two pinned derivations +of §4 in its own arithmetic, writes the digital source type of §5 and the C2PA statement of §6 +itself, and verifies each room with the live-rooms corpus's second implementation +(live_rooms_verify.py). It reads §10.2's pinned icons and §12's codes from the extension. + + python3 -I synthesis_verify.py + +It gives every vector of the corpus the outcome, the reason, the place and the statements the +corpus states, checks that every reason of §12 refuses a vector, and then mutates copies of the +corpus and requires every copy to fail. Exit status 0 only if all of that holds. Python 3.8 or +later; standard library only. +""" + +import copy +import hashlib +import json +import os +import re +import sys +import unicodedata + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance")) +import live_events_verify as LEV # noqa: E402 +import live_rooms_verify as LRV # noqa: E402 +import _ed25519 as ED # noqa: E402 +from _blake3 import blake3 # noqa: E402 + +EXTENSION = os.path.join(HERE, "..", "extensions", "synthesis.md") +REGISTRY = os.path.join(HERE, "..", "registry", "capabilities.json") + +SIGN_DOMAIN = b"wai:synthesis\x01" # §2 +LINK_DOMAIN = b"wai:synthesis-link\x01" # §2 +OBJECT_DOMAIN = b"moq-jwp:object\x01" # jwp-receipts §2.1 +IPTC = "http://cv.iptc.org/newscodes/digitalsourcetype/" +MODEL_ID = "tag:transaction.science,2026:wai/model/" # §6 +UPSCALE2, DENOISE3 = "wai-synthesis:upscale2", "wai-synthesis:denoise3" +MAX_INT = (1 << 53) - 1 + +CLASSES = ["captured", "reconstructed", "assistive", "synthesized"] # §3.2, in order +STEP_CLASS = {"capture": "captured", "convert": "reconstructed", "assist": "assistive", "generate": "synthesized"} +CAPTURE_SOURCES = ["digitalCapture", "computationalCapture", "screenCapture", "digitalCreation"] +SOURCES = CAPTURE_SOURCES + ["algorithmicallyEnhanced", "trainedAlgorithmicMedia", "compositeWithTrainedAlgorithmicMedia", + "algorithmicMedia", "composite", "compositeCapture", "compositeSynthetic"] # §5 +OVERSIGHT = ["fully_autonomous", "prompt_guided", "human_validated"] +MEDIA = ["audio", "video", "image", "text"] +PATH_TIERS = ["decode-equivalence", "generation-equivalence", "entropy-consistency", "none"] +REVIEW_TIERS = ["decode-equivalence", "generation-equivalence"] + +# Unicode's White_Space property (a term holds none of these, jwp-receipts §2.4.3). +WHITE_SPACE = set("\t\n\x0b\x0c\r \x85\xa0         " + "   

   ") + + +class Refuse(Exception): + def __init__(self, reason, at=None): + super().__init__(reason) + self.reason, self.at = reason, at + + +class Bad(Exception): + """A record that does not read: `record_malformed`.""" + + +# ---------------------------------------------------------------- §2 records + +def strict_json(text): + """RFC 8259, no member named twice, no number but an unsigned integer, no unpaired + surrogate, at most 32 levels.""" + def pairs(ps): + d = {} + for k, v in ps: + if k in d: + raise Bad("a member named twice") + d[k] = v + return d + + def no_float(s): + raise Bad("a number that is not an unsigned integer") + + def integer(s): + if s.startswith("-"): + raise Bad("a negative number") + return int(s) + + try: + v = json.loads(text, object_pairs_hook=pairs, parse_float=no_float, parse_int=integer, parse_constant=no_float) + except (ValueError, RecursionError): + raise Bad("not JSON") + + def walk(x, depth): + if isinstance(x, (dict, list)): + if depth > 32: + raise Bad("too deep") + for y in (x.values() if isinstance(x, dict) else x): + walk(y, depth + 1) + if isinstance(x, dict): + for k in x: + walk(k, depth) + elif isinstance(x, str): + if any(0xD800 <= ord(c) <= 0xDFFF for c in x): + raise Bad("an unpaired surrogate") + elif isinstance(x, bool): + raise Bad("a boolean") + elif isinstance(x, int): + if x > MAX_INT: + raise Bad("an integer past 2^53 - 1") + walk(v, 1) + return v + + +def jstr(s): + out = ['"'] + for c in s: + o = ord(c) + if c == '"': + out.append('\\"') + elif c == "\\": + out.append("\\\\") + elif c == "\b": + out.append("\\b") + elif c == "\f": + out.append("\\f") + elif c == "\n": + out.append("\\n") + elif c == "\r": + out.append("\\r") + elif c == "\t": + out.append("\\t") + elif o < 0x20: + out.append("\\u%04x" % o) + else: + out.append(c) + out.append('"') + return "".join(out) + + +def jcs(v): + """RFC 8785 on the values a record carries.""" + if v is None: + return "null" + if isinstance(v, bool): + raise Bad("a boolean") + if isinstance(v, int): + return str(v) + if isinstance(v, str): + return jstr(v) + if isinstance(v, list): + return "[" + ",".join(jcs(x) for x in v) + "]" + keys = sorted(v, key=lambda k: k.encode("utf-16-be")) + return "{" + ",".join(jstr(k) + ":" + jcs(v[k]) for k in keys) + "}" + + +def link_hash(v): + return blake3(LINK_DOMAIN + jcs(v).encode("utf-8")) + + +def signing_bytes(v): + return SIGN_DOMAIN + jcs({k: x for k, x in v.items() if k != "sig"}).encode("utf-8") + + +def need(cond): + if not cond: + raise Bad("a member breaks its rule") + + +def hexb(v, n): + need(isinstance(v, str) and len(v) == 2 * n and re.fullmatch(r"[0-9a-f]*", v) is not None) + return bytes.fromhex(v) + + +def opt_hex(v, n): + return None if v is None else hexb(v, n) + + +def integer(v): + need(isinstance(v, int) and not isinstance(v, bool) and 0 <= v <= MAX_INT) + return v + + +def term(v): + need(isinstance(v, str) and 0 < len(v.encode("utf-8")) <= 2048 + and not any(c in WHITE_SPACE or unicodedata.category(c) == "Cc" for c in v)) + return v + + +def opt_term(v): + return None if v is None else term(v) + + +def one_of(v, values): + need(isinstance(v, str) and v in values) + return v + + +def members(v, names): + need(isinstance(v, dict) and set(v) == set(names)) + return v + + +def date_time(s): + """RFC 3339 §5.6 date-time, with §5.7's days (jwp-receipts §2.4.3).""" + m = re.fullmatch(r"([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt]([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?([Zz]|[+-]([0-9]{2}):([0-9]{2}))", s or "") + need(m is not None) + y, mo, d, h, mi, sec = (int(m.group(i)) for i in range(1, 7)) + leap = y % 4 == 0 and (y % 100 != 0 or y % 400 == 0) + days = [31, 29 if leap else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + need(1 <= mo <= 12 and 1 <= d <= days[mo - 1] and h <= 23 and mi <= 59 and sec <= 60) + if m.group(9) is not None: + need(int(m.group(9)) <= 23 and int(m.group(10)) <= 59) + + +def read(text, kind, names, fields): + """§2: read one record, then its signature. `fields` reads the kind's own members.""" + try: + v = strict_json(text) + members(v, names) + need(v["kind"] == kind) + signer = hexb(v["signer_pubkey"], 32) + sig = hexb(v["sig"], 64) + if "issued_at" in v: + date_time(v["issued_at"]) + r = fields(v) + except (Bad, KeyError, TypeError, AttributeError): + raise Refuse("record_malformed") + bad = ED.verify(signer, signing_bytes(v), sig) + if bad: + raise Refuse(bad) + r.update(value=v, signer=signer, link=link_hash(v)) + return r + + +# ---------------------------------------------------------------- §3 the origin claim + +ORIGIN = ["kind", "subject", "media", "class", "step", "operation", "record", "source", "models", "oversight", "inputs", + "evidence", "issued_at", "signer_pubkey", "sig"] + + +def subject_of(v): + t = v.get("type") + if t == "object": + members(v, ["type", "content_hash"]) + return ("object", hexb(v["content_hash"], 32)) + if t == "group": + members(v, ["type", "receipt"]) + return ("group", hexb(v["receipt"], 32)) + if t == "track": + members(v, ["type", "track", "from_group"]) + integer(v["from_group"]) + return ("track", hexb(v["track"], 32)) + raise Bad("a subject of no type") + + +def origin_fields(v): + c = {"subject": subject_of(v["subject"]), "media": one_of(v["media"], MEDIA), "class": one_of(v["class"], CLASSES), + "step": one_of(v["step"], list(STEP_CLASS)), "operation": term(v["operation"]), "record": opt_hex(v["record"], 32), + "source": one_of(v["source"], SOURCES), + "oversight": None if v["oversight"] is None else one_of(v["oversight"], OVERSIGHT)} + need(isinstance(v["models"], list) and len(v["models"]) <= 16) + c["models"] = [] + for m in v["models"]: + members(m, ["digest", "type"]) + c["models"].append({"digest": hexb(m["digest"], 32), "trained": one_of(m["type"], ["trained", "algorithmic"]) == "trained"}) + need(len({m["digest"] for m in c["models"]}) == len(c["models"])) + need(isinstance(v["inputs"], list) and len(v["inputs"]) <= 16) + c["inputs"] = [] + for i in v["inputs"]: + members(i, ["content_hash", "origin", "class", "source"]) + c["inputs"].append({"content_hash": hexb(i["content_hash"], 32), "origin": opt_hex(i["origin"], 32), + "class": one_of(i["class"], CLASSES), "source": one_of(i["source"], SOURCES)}) + need(len({i["content_hash"] for i in c["inputs"]}) == len(c["inputs"])) + e = v["evidence"] + if e is None: + c["evidence"] = None + else: + members(e, ["tier", "derivation", "params", "width", "height", "input_samples", "output_samples"]) + need(isinstance(e["params"], dict)) + c["evidence"] = {"tier": one_of(e["tier"], ["replicate", "attest"]), "derivation": term(e["derivation"]), + "params": e["params"], "width": integer(e["width"]), "height": integer(e["height"]), + "input_samples": hexb(e["input_samples"], 32), "output_samples": hexb(e["output_samples"], 32)} + need(c["evidence"]["width"] >= 1 and c["evidence"]["height"] >= 1) + n, m, s = len(c["inputs"]), len(c["models"]), c["step"] + plain = c["oversight"] is None and c["evidence"] is None + if s == "capture": + need(n == 0 and m == 0 and plain and c["source"] in CAPTURE_SOURCES) + elif s == "convert": + need(n >= 1 and m == 0 and plain) + elif s == "assist": + need(n == 1 and c["oversight"] is None and c["evidence"] is not None) + else: + need(m >= 1 and c["oversight"] is not None and c["evidence"] is None) + return c + + +def class_of(c): + """§3.2: the greatest of the step's class and the inputs'.""" + return max([STEP_CLASS[c["step"]]] + [i["class"] for i in c["inputs"]], key=CLASSES.index) + + +def generated_source(c): + """§5.""" + if not any(m["trained"] for m in c["models"]): + return "algorithmicMedia" + if c["oversight"] == "human_validated" or any(i["class"] != "synthesized" for i in c["inputs"]): + return "compositeWithTrainedAlgorithmicMedia" + return "trainedAlgorithmicMedia" + + +def source_of(c): + """§5's table.""" + ins = c["inputs"] + if c["step"] == "capture": + return c["source"] + if c["step"] == "convert": + if len(ins) == 1: + return ins[0]["source"] + if any(i["class"] == "synthesized" for i in ins): + return "compositeSynthetic" + if all(i["source"] in ("digitalCapture", "computationalCapture") for i in ins): + return "compositeCapture" + return "composite" + if c["step"] == "assist": + return ins[0]["source"] if ins[0]["class"] == "synthesized" else "algorithmicallyEnhanced" + return generated_source(c) + + +def statement(c): + """§6, as its JCS text.""" + if len(c["inputs"]) != 1: + actions = [{"action": "c2pa.created", "digitalSourceType": IPTC + c["source"]}] + elif c["step"] == "convert": + actions = [{"action": "c2pa.opened"}, {"action": "c2pa.converted"}] + elif c["step"] == "assist": + actions = [{"action": "c2pa.opened"}, {"action": "c2pa.edited", "digitalSourceType": IPTC + "algorithmicallyEnhanced"}] + else: + actions = [{"action": "c2pa.opened"}, {"action": "c2pa.edited", "digitalSourceType": IPTC + generated_source(c)}] + disclosures = [] + for m in [m for m in c["models"] if m["trained"]]: + data = {"modelType": "c2pa.types.model", "modelIdentifier": MODEL_ID + m["digest"].hex()} + if c["step"] == "generate": + data["contentProfile"] = {"humanOversightLevel": c["oversight"]} + k = len(disclosures) + disclosures.append({"label": "c2pa.ai-disclosure" if k == 0 else "c2pa.ai-disclosure__%d" % k, "data": data}) + return jcs({"actions": actions, "assertions": disclosures}) + + +def payload_of(c): + """§7.2: the class's code, then seven bytes of the claim's link hash.""" + return bytes([CLASSES.index(c["class"])]) + c["link"][:7] + + +# ---------------------------------------------------------------- §4 the derivations + +def upscale2(px, w, h): + out = bytearray(4 * w * h * 3) + ow = 2 * w + + def at(x, y, k): + return px[(y * w + x) * 3 + k] + for y in range(h): + for x in range(w): + x1, y1 = min(x + 1, w - 1), min(y + 1, h - 1) + for k in range(3): + a, b, c, d = at(x, y, k), at(x1, y, k), at(x, y1, k), at(x1, y1, k) + out[((2 * y) * ow + 2 * x) * 3 + k] = a + out[((2 * y) * ow + 2 * x + 1) * 3 + k] = (a + b + 1) >> 1 + out[((2 * y + 1) * ow + 2 * x) * 3 + k] = (a + c + 1) >> 1 + out[((2 * y + 1) * ow + 2 * x + 1) * 3 + k] = (a + b + c + d + 2) >> 2 + return bytes(out) + + +def denoise3(px, w, h): + out = bytearray(w * h * 3) + for y in range(h): + for x in range(w): + for k in range(3): + s = 8 + for j in (-1, 0, 1): + for i in (-1, 0, 1): + weight = (2 if i == 0 else 1) * (2 if j == 0 else 1) + xx, yy = min(max(x + i, 0), w - 1), min(max(y + j, 0), h - 1) + s += weight * px[(yy * w + xx) * 3 + k] + out[(y * w + x) * 3 + k] = s >> 4 + return bytes(out) + + +DERIVATIONS = {UPSCALE2: (upscale2, 4), DENOISE3: (denoise3, 1)} + + +# ---------------------------------------------------------------- §7, §9, §10 the other records + +def marking_fields(v): + return {"subject": hexb(v["subject"], 32), "origin": hexb(v["origin"], 32), "scheme": term(v["scheme"]), + "scheme_digest": hexb(v["scheme_digest"], 32), "payload": hexb(v["payload"], 8)} + + +def detection_fields(v): + return {"subject": hexb(v["subject"], 32), "scheme": term(v["scheme"]), "scheme_digest": hexb(v["scheme_digest"], 32), + "payload": opt_hex(v["payload"], 8)} + + +def review_fields(v): + verdict = one_of(v["verdict"], ["approved", "approved-with-notes", "rejected"]) + notes = opt_hex(v["notes"], 32) + need((notes is None) == (verdict == "approved")) + term(v["decoder"]) + opt_term(v["variant"]) + opt_term(v["role"]) + one_of(v["scope"], ["accuracy", "rendering", "translation"]) + return {"subject": hexb(v["subject"], 32), "origin": hexb(v["origin"], 32), "tier": one_of(v["tier"], REVIEW_TIERS), + "reconstruction": hexb(v["reconstruction"], 32), "verdict": verdict} + + +def policy_fields(v): + need(isinstance(v["classes"], list) and len(v["classes"]) >= 1) + idx = [CLASSES.index(one_of(c, CLASSES)) for c in v["classes"]] + need(all(a < b for a, b in zip(idx, idx[1:]))) + interval = None if v["interval_us"] is None else integer(v["interval_us"]) + need(interval != 0) + return {"classes": list(v["classes"]), "visual": hexb(v["visual"], 32), "audio": opt_hex(v["audio"], 32), + "interval_us": interval, "grace_us": integer(v["grace_us"])} + + +def entry_of(e): + t = e.get("type") + if t == "present": + members(e, ["type", "subject", "origin", "class", "media", "tier", "decoder", "variant", "output"]) + term(e["decoder"]) + opt_term(e["variant"]) + return {"type": t, "subject": hexb(e["subject"], 32), "origin": opt_hex(e["origin"], 32), + "class": None if e["class"] is None else one_of(e["class"], CLASSES), "media": one_of(e["media"], MEDIA), + "tier": one_of(e["tier"], PATH_TIERS), "output": opt_hex(e["output"], 32)} + if t == "shown": + members(e, ["type", "label", "asset", "trigger"]) + return {"type": t, "audio": one_of(e["label"], ["visual", "audio"]) == "audio", "asset": hexb(e["asset"], 32), + "trigger": one_of(e["trigger"], ["first-exposure", "resume", "interval"])} + if t == "resume": + members(e, ["type", "after"]) + one_of(e["after"], ["ad-break", "other"]) + return {"type": t} + if t == "review": + members(e, ["type", "subject", "review"]) + return {"type": t, "subject": hexb(e["subject"], 32), "review": hexb(e["review"], 32)} + raise Bad("an entry of no type") + + +def player_fields(v): + return {"session": hexb(v["session"], 32), "seq": integer(v["seq"]), "prev": opt_hex(v["prev"], 32), "at": integer(v["at"]), + "entry": entry_of(v["entry"])} + + +# ---------------------------------------------------------------- §11 verification + +def generates(env, classes): + """§3.3: an envelope whose capability's registry class is generative-exact or behavioral, or + whose intent is create.""" + if len(env) < 8 or env[:4] != b"WAI1": + return False + n = int.from_bytes(env[4:8], "little") + try: + m = json.loads(env[8:8 + n]) + except ValueError: + return False + cap = m.get("model_requirement", {}).get("capability", "") + return classes.get(cap) in ("generative-exact", "behavioral") or m.get("intent") == "create" + + +def track_digest(room, owner, media, layer): + """live-rooms §6.2.""" + return hashlib.sha256(("-".join([room.hex(), owner, media]) + "\x00" + str(layer)).encode()).digest() + + +def verify(case, classes): + """§11.2: the statements of §11.3, or Refuse with its reason and place.""" + st = {"claims": [], "detections": [], "ai_tracks": [], "reviews_presented": [], "labels": [], "visual_icon": None, "room": None} + + def each(key, kind, names, fields, place): + out = [] + for i, t in enumerate(case[key]): + try: + out.append(read(t, kind, names, fields)) + except Refuse as e: + raise Refuse(e.reason, [place, i]) + return out + + # Step 1. + claims = each("claims", "wai-origin", ORIGIN, origin_fields, "claim") + markings = each("markings", "wai-marking", ["kind", "subject", "origin", "scheme", "scheme_digest", "payload", "issued_at", "signer_pubkey", "sig"], + marking_fields, "marking") + detections = each("detections", "wai-detection", ["kind", "subject", "scheme", "scheme_digest", "payload", "issued_at", "signer_pubkey", "sig"], + detection_fields, "detection") + reviews = each("reviews", "wai-review", ["kind", "subject", "origin", "tier", "reconstruction", "decoder", "variant", "scope", "verdict", "notes", + "role", "issued_at", "signer_pubkey", "sig"], review_fields, "review") + policy = None + if case["label_policy"] is not None: + try: + policy = read(case["label_policy"], "wai-label-policy", ["kind", "classes", "visual", "audio", "interval_us", "grace_us", "issued_at", + "signer_pubkey", "sig"], policy_fields) + except Refuse as e: + raise Refuse(e.reason, ["label_policy"]) + log = each("player", "wai-player", ["kind", "session", "seq", "prev", "at", "entry", "signer_pubkey", "sig"], player_fields, "player") + + by_link = {c["link"]: c for c in claims} + objects = {blake3(OBJECT_DOMAIN + bytes.fromhex(o)): bytes.fromhex(o) for o in case["objects"]} + decoded = {bytes.fromhex(d["object"]): bytes.fromhex(d["samples"]) for d in case["decoded"]} + sha = lambda b: hashlib.sha256(b).digest() # noqa: E731 + + # Step 2. + seen = set() + for i, c in enumerate(claims): + def no(reason): + raise Refuse(reason, ["claim", i]) + key = jcs(c["value"]["subject"]) + if key in seen: + no("claim_conflict") + seen.add(key) + if c["class"] != class_of(c): + no("class_mismatch") + if c["source"] != source_of(c): + no("source_mismatch") + for inp in c["inputs"]: + held = by_link.get(inp["origin"]) + if held is not None and (held["subject"] != ("object", inp["content_hash"]) or held["class"] != inp["class"] + or held["source"] != inp["source"]): + no("input_mismatch") + kind, h = c["subject"] + if kind == "object" and h in objects and generates(objects[h], classes) and c["step"] != "generate": + no("class_understated") + status = None + e = c["evidence"] + if e is not None: + w, hh = e["width"], e["height"] + if e["tier"] == "replicate": + if e["derivation"] not in DERIVATIONS or e["params"] != {} or DERIVATIONS[e["derivation"]][1] * w * hh * 3 > (1 << 26): + no("evidence_invalid") + status = "not-recomputed" + px = decoded.get(c["inputs"][0]["content_hash"]) + if px is not None: + if len(px) != w * hh * 3 or sha(px) != e["input_samples"]: + no("input_samples_mismatch") + if sha(DERIVATIONS[e["derivation"]][0](px, w, hh)) != e["output_samples"]: + no("assistive_recompute_mismatch") + status = "proved" + else: + status = "asserted" + if kind == "object" and h in decoded and sha(decoded[h]) != e["output_samples"]: + no("samples_mismatch") + if case["policy"]["assistive"] == "require-proof" and status != "proved": + no("assistive_not_proved") + st["claims"].append({"link": c["link"].hex(), "class": c["class"], "source": c["source"], "c2pa": statement(c), "assistive": status}) + + # Step 3. + for i, m in enumerate(markings): + c = by_link.get(m["origin"]) + if c is None: + raise Refuse("claim_unknown", ["marking", i]) + if c["subject"] != ("object", m["subject"]) or payload_of(c) != m["payload"]: + raise Refuse("marking_mismatch", ["marking", i]) + for i, d in enumerate(detections): + claim = next((c for c in claims if c["subject"] == ("object", d["subject"])), None) + marking = next((m for m in markings if m["subject"] == d["subject"]), None) + if marking is not None and (marking["scheme"], marking["scheme_digest"]) != (d["scheme"], d["scheme_digest"]): + raise Refuse("detection_mismatch", ["detection", i]) + if d["payload"] is None: + if marking is not None: + raise Refuse("watermark_absent", ["detection", i]) + result = "unmarked" + elif claim is None: + result = "not-checked" + elif d["payload"] != payload_of(claim): + raise Refuse("layers_disagree", ["detection", i]) + else: + result = "agree" + st["detections"].append({"subject": d["subject"].hex(), "result": result}) + if case["policy"]["dual_layer"] == "required": + for i, c in enumerate(claims): + if c["class"] == "synthesized" and c["subject"][0] == "object" and not any(m["origin"] == c["link"] for m in markings): + raise Refuse("layer_missing", ["claim", i]) + + # Step 4. + for i, r in enumerate(reviews): + c = by_link.get(r["origin"]) + if c is None: + raise Refuse("claim_unknown", ["review", i]) + if c["subject"] != ("object", r["subject"]) or c["class"] != "synthesized": + raise Refuse("review_subject_mismatch", ["review", i]) + + # Step 5. + room = case["room"] + if room is not None: + chains = [[bytes.fromhex(e) for e in ch] for ch in room["chains"]] + grants = [bytes.fromhex(g) for g in room["grants"]] + out = LRV.verify_room(bytes.fromhex(room["descriptor"]), grants, room["key_log"], chains) + if out["outcome"] == "refuse": + raise Refuse(out["reason"], ["room"] + list(out["at"])) + st["room"] = out["outcome"] + d = LRV.read_descriptor(bytes.fromhex(room["descriptor"])) + held, _ = LRV.check_grants(d, grants) + by_digest = {g["digest"]: g for g in held} + current = {} + for c_i, r_i, under in out["order"]: + if under is not None: + continue + ev = LEV.read_record(chains[c_i][r_i]) + if not 0x10 <= ev["kind"] <= 0x16: + continue + subject, x = ev["payload"][:32], LRV.read_detail(ev["kind"], ev["payload"][32:]) + if ev["kind"] == 0x10: + current[subject] = x["grant"] + continue + if ev["kind"] == 0x14: + current[subject] = x["new"] + continue + if ev["kind"] == 0x11: + current.pop(subject, None) + continue + if ev["kind"] not in (0x12, 0x15): + continue + g = by_digest.get(current.get(subject)) + model = None + for k, v in (LRV.read_attrs(g["attrs"]) or []) if g else []: + if k == "ai": + model = bytes.fromhex(v) + if model is None: + continue + owner = subject.hex() if not x["slot"] else "_" + x["slot"] + media = {1: "audio", 2: "video", 3: "screen"}[x["media"]] + for layer in range(x["layers"]): + t = track_digest(d["room"], owner, media, layer) + found = next((c for c in claims if c["subject"] == ("track", t) and c["class"] == "synthesized" and c["step"] == "generate" + and any(m["digest"] == model for m in c["models"]) + and (c["signer"] == subject or LRV.is_room_key(d, c["signer"]))), None) + if found is None: + raise Refuse("ai_track_unlabelled", ["room", "record", c_i, r_i]) + st["ai_tracks"].append({"track": t.hex(), "claim": found["link"].hex()}) + + # Step 6. + for j, p in enumerate(log): + ok = p["seq"] == j and p["session"] == log[0]["session"] and p["signer"] == log[0]["signer"] + ok = ok and (p["prev"] is None if j == 0 else (p["prev"] == log[j - 1]["link"] and p["at"] >= log[j - 1]["at"])) + if not ok: + raise Refuse("player_chain_broken", ["player", j]) + for j, p in enumerate(log): + e = p["entry"] + if e["type"] == "present": + c = by_link.get(e["origin"]) if e["origin"] is not None else None + if c is not None and (c["subject"] != ("object", e["subject"]) or e["class"] != c["class"]): + raise Refuse("present_mismatch", ["player", j]) + if e["output"] is not None and e["subject"] in decoded and sha(decoded[e["subject"]]) != e["output"]: + raise Refuse("present_output_irreproducible", ["player", j]) + elif e["type"] == "review": + r = next((r for r in reviews if r["link"] == e["review"]), None) + if r is None: + raise Refuse("claim_unknown", ["player", j]) + last = next((q["entry"] for q in reversed(log[:j]) if q["entry"]["type"] == "present" and q["entry"]["subject"] == e["subject"]), None) + if last is None or r["subject"] != e["subject"]: + raise Refuse("review_subject_mismatch", ["player", j]) + if r["verdict"] == "rejected": + raise Refuse("review_rejected", ["player", j]) + if last["tier"] != r["tier"]: + raise Refuse("review_path_not_exact", ["player", j]) + if last["output"] != r["reconstruction"]: + raise Refuse("review_reconstruction_mismatch", ["player", j]) + st["reviews_presented"].append({"entry": j, "review": e["review"].hex()}) + if policy is not None: + st["visual_icon"] = ICONS.get(policy["visual"].hex()) + for j, p in enumerate(log): + e = p["entry"] + if e["type"] == "shown" and (policy["audio"] if e["audio"] else policy["visual"]) != e["asset"]: + raise Refuse("label_asset_mismatch", ["player", j]) + + def shown_in(audio, lo, hi): + return next((k for k, q in enumerate(log) if q["entry"]["type"] == "shown" and q["entry"]["audio"] == audio and lo <= q["at"] <= hi), None) + + last = {False: None, True: None} + for j, p in enumerate(log): + e = p["entry"] + if e["type"] != "present" or e["class"] not in policy["classes"]: + continue + audio = e["media"] == "audio" + hi = p["at"] + policy["grace_us"] + prev = last[audio] + if prev is None: + trigger, k = "first-exposure", shown_in(audio, 0, hi) + else: + resumes = [q["at"] for q in log[prev + 1:j] if q["entry"]["type"] == "resume"] + if resumes: + trigger, k = "resume", shown_in(audio, resumes[-1], hi) + elif policy["interval_us"] is not None: + trigger, k = "interval", shown_in(audio, max(p["at"] - policy["interval_us"], 0), hi) + else: + last[audio] = j + continue + if k is None: + raise Refuse("label_not_shown", ["player", j]) + st["labels"].append({"entry": j, "trigger": trigger, "shown": k}) + last[audio] = j + return st + + +# ---------------------------------------------------------------- the extension's tables + +def section(text, head): + at = text.index("\n" + head) + nxt = re.search(r"\n#{2,3} ", text[at + 1:]) + return text[at: at + 1 + nxt.start()] if nxt else text[at:] + + +with open(EXTENSION, encoding="utf-8") as _f: + _TEXT = _f.read() +ICONS = {} +for _line in section(_TEXT, "### 10.2 ").splitlines(): + _m = re.fullmatch(r"\| `(LABEL_AI[^`]*)` \| `([0-9a-f]{64})` \|", _line.strip()) + if _m: + ICONS[_m.group(2)] = _m.group(1) +REASONS = [l.split("`")[1] for l in section(_TEXT, "## 12. Reasons").splitlines() if l.startswith("| `")] + + +def outcome(v, classes): + try: + return {"outcome": "accept", "statements": verify(v, classes)} + except Refuse as e: + return {"outcome": "refuse", "reason": e.reason, "at": e.at} + + +def stated(v): + return {k: v[k] for k in ("outcome", "reason", "at", "statements") if k in v} + + +def check(doc, classes): + """Failures of a corpus document: an outcome other than the one stated, or a table that + differs from the extension's.""" + fails = [] + pins = {d["sha256"]: d["file"] for d in doc["eu_icons"]} + if pins != ICONS or len(doc["eu_icons"]) != len(ICONS): + fails.append("the corpus's EU icons differ from §10.2's table") + for v in doc["vectors"]: + try: + got = outcome(v, classes) + except Exception as e: # noqa: BLE001 — a malformed copy fails, whatever it breaks + got = {"outcome": "unreadable: %r" % (e,)} + if got != stated(v): + fails.append("%s: %s" % (v["name"], json.dumps(got)[:300])) + return fails + + +def flip_hex_digit(s, at): + c = "0" if s[at] != "0" else "1" + return s[:at] + c + s[at + 1:] + + +def mutations(doc): + """Copies of the corpus, each with one defect a conforming verifier must catch.""" + vn = {v["name"]: k for k, v in enumerate(doc["vectors"])} + + def mutate(label, f): + d = copy.deepcopy(doc) + f(d["vectors"]) + return label, d + + chain, gen, room, cert = vn["chain"], vn["generated"], vn["ai_room"], vn["certified"] + + def retext(vs, i, key, k, f): + """Rewrite record k of list `key` of vector i as JSON, applying f to its value.""" + v = json.loads(vs[i][key][k]) + f(v) + vs[i][key][k] = json.dumps(v) + + def decoded_byte(vs): + d = vs[chain]["decoded"][0] + d["samples"] = flip_hex_digit(d["samples"], 0) + + def upscale_digit(vs): + retext(vs, chain, "claims", 2, lambda v: v["evidence"].update(output_samples=flip_hex_digit(v["evidence"]["output_samples"], 5))) + + def detection_class(vs): + retext(vs, gen, "detections", 0, lambda v: v.update(payload="00" + v["payload"][2:])) + + return [ + mutate("a digit of the upscale's signature", lambda vs: retext(vs, chain, "claims", 2, lambda v: v.update(sig=flip_hex_digit(v["sig"], 9)))), + mutate("the upscale's output digest changed", upscale_digit), + mutate("a byte of the reconstruction's decoded samples", decoded_byte), + mutate("the reconstruction's claim withheld from the chain", lambda vs: vs[chain]["claims"].pop(1)), + mutate("the generated video's detection reading class captured", detection_class), + mutate("the generated video's marking withheld", lambda vs: vs[gen]["markings"].pop(0)), + mutate("the composite's source stated compositeCapture", + lambda vs: vs[gen]["statements"]["claims"][4].update(source="compositeCapture")), + mutate("the transcode's statement stated as created", + lambda vs: vs[gen]["statements"]["claims"][5].update(c2pa=vs[gen]["statements"]["claims"][1]["c2pa"])), + mutate("the AI host's track claim withheld", lambda vs: vs[room].update(claims=[])), + mutate("the AI host's grant withheld from the room", lambda vs: vs[room]["room"]["grants"].pop(0)), + mutate("the certified log's first label withheld", lambda vs: vs[cert]["player"].pop(0)), + mutate("the label after the break stated as an interval", + lambda vs: vs[cert]["statements"]["labels"][1].update(trigger="interval")), + mutate("the review stated at generation-equivalence", + lambda vs: retext(vs, cert, "reviews", 0, lambda v: v.update(tier="generation-equivalence"))), + mutate("the presented video's decoded frame changed", + lambda vs: vs[cert]["decoded"][0].update(samples=flip_hex_digit(vs[cert]["decoded"][0]["samples"], 3))), + mutate("the policy's visual stated as another pinned icon", + lambda vs: vs[cert]["statements"].update(visual_icon="LABEL_AI_black.svg")), + mutate("the first refusal stated at another place", lambda vs: vs[next(k for k, v in enumerate(vs) if v["outcome"] == "refuse")].update(at=["claim", 1])), + ("an EU icon's pin changed", dict(copy.deepcopy(doc), eu_icons=[dict(doc["eu_icons"][0], sha256="00" * 32)] + doc["eu_icons"][1:])), + ] + + +def main(corpus_dir): + fails = [] + st = ED._self_test() + print(("PASS" if not st else "FAIL") + " Ed25519 strict rule self-test " + ",".join(st)) + if st: + fails.append("ed25519 self-test") + with open(REGISTRY, encoding="utf-8") as f: + classes = {c["capability"]: c["replicate"] for c in json.load(f)["capabilities"]} + with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f: + doc = json.load(f) + if len(ICONS) != 24: + fails.append("§10.2's table does not read as 24 pinned icons") + reasons_seen = set() + for v in doc["vectors"]: + try: + got = outcome(v, classes) + except Exception as e: # noqa: BLE001 + got = {"outcome": "unreadable: %r" % (e,)} + ok = got == stated(v) + if v.get("reason"): + reasons_seen.add(v["reason"]) + print(("PASS" if ok else "FAIL") + " vector %s: %s" % (v["name"], got["outcome"] + (" %s at %s" % (got["reason"], got["at"]) if "reason" in got else ""))) + if not ok: + fails.append("%s: %s, the corpus states %s" % (v["name"], json.dumps(got)[:400], json.dumps(stated(v))[:400])) + table = check(doc, classes) + if any("EU icons" in t for t in table): + fails.append("the corpus's EU icons differ from §10.2's table") + missing = [r for r in REASONS if r not in reasons_seen] + if missing: + fails.append("no vector refused with %s" % ", ".join(missing)) + accepted = {v["name"] for v in doc["vectors"] if v["outcome"] == "accept"} + want = {"chain", "assistive_asserted", "generated", "ai_room", "certified"} + if not want <= accepted: + fails.append("vectors not accepted: %s" % ", ".join(sorted(want - accepted))) + print("%s every reason of §12 refuses a vector, and every case of §13 is accepted" % ("PASS" if not missing and want <= accepted else "FAIL")) + for label, d in mutations(doc): + caught = check(d, classes) + print(("PASS" if caught else "FAIL") + " mutated copy fails: %s%s" % (label, " (%s)" % caught[0][:100] if caught else "")) + if not caught: + fails.append("a mutated copy verifies: %s" % label) + for f in fails: + print("FAIL " + f) + print(("PASS" if not fails else "FAIL") + " synthesis corpus: %d vectors" % len(doc["vectors"])) + return 1 if fails else 0 + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("usage: synthesis_verify.py ", file=sys.stderr) + sys.exit(2) + sys.exit(main(sys.argv[1])) diff --git a/wai/registry/capabilities.json b/wai/registry/capabilities.json index b7996ab2a..e798442de 100644 --- a/wai/registry/capabilities.json +++ b/wai/registry/capabilities.json @@ -770,6 +770,20 @@ "edition": null, "registered": "2026-10-20" }, + { + "capability": "wai.media.synthesis", + "kind": "record", + "status": "registered", + "media": null, + "role": null, + "tier": null, + "licence": null, + "replicate": null, + "sets": [], + "spec_section": "§5 Interactive, temporal & computed content", + "edition": null, + "registered": "2026-10-21" + }, { "capability": "wai.mesh.draco", "kind": "payload", @@ -2319,6 +2333,13 @@ "section": "§5.1", "status": "defined" }, + { + "name": "wai-detection", + "kind": "document-kind", + "document": "extensions/synthesis.md", + "section": "§7.3", + "status": "defined" + }, { "name": "wai-key-status", "kind": "document-kind", @@ -2326,6 +2347,20 @@ "section": "§7.9", "status": "defined" }, + { + "name": "wai-label-policy", + "kind": "document-kind", + "document": "extensions/synthesis.md", + "section": "§10.1", + "status": "defined" + }, + { + "name": "wai-marking", + "kind": "document-kind", + "document": "extensions/synthesis.md", + "section": "§7.2", + "status": "defined" + }, { "name": "wai-module-grant", "kind": "document-kind", @@ -2340,6 +2375,20 @@ "section": "§8.4", "status": "defined" }, + { + "name": "wai-origin", + "kind": "document-kind", + "document": "extensions/synthesis.md", + "section": "§3.1", + "status": "defined" + }, + { + "name": "wai-player", + "kind": "document-kind", + "document": "extensions/synthesis.md", + "section": "§10.3", + "status": "defined" + }, { "name": "wai-receipt-index", "kind": "document-kind", @@ -2354,6 +2403,13 @@ "section": "§4.1", "status": "defined" }, + { + "name": "wai-review", + "kind": "document-kind", + "document": "extensions/synthesis.md", + "section": "§9.1", + "status": "defined" + }, { "name": "wai1", "kind": "codecs-prefix", diff --git a/wai/vocab/v2.ttl b/wai/vocab/v2.ttl index 094f06173..b1f55fa52 100644 --- a/wai/vocab/v2.ttl +++ b/wai/vocab/v2.ttl @@ -618,6 +618,13 @@ wai:wai.live.watermark a wai:Capability ; wai:specSection "§5 Interactive, temporal & computed content" ; wai:registeredOn "2026-10-20"^^xsd:date . +wai:wai.media.synthesis a wai:Capability ; + rdfs:label "wai.media.synthesis" ; + wai:kind wai:kind-record ; + wai:status wai:status-registered ; + wai:specSection "§5 Interactive, temporal & computed content" ; + wai:registeredOn "2026-10-21"^^xsd:date . + wai:wai.mesh.draco a wai:Capability ; rdfs:label "wai.mesh.draco" ; wai:kind wai:kind-payload ; diff --git a/wai/wai-live-web/src/lib.rs b/wai/wai-live-web/src/lib.rs index 597da7271..7c18b3bff 100644 --- a/wai/wai-live-web/src/lib.rs +++ b/wai/wai-live-web/src/lib.rs @@ -451,7 +451,8 @@ fn payload_json(ev: &Event) -> Value { "lang": c.lang, "text": c.text, }), - Payload::Chat { text } => json!({"text": text}), + Payload::Chat { text, origin: None } => json!({"text": text}), + Payload::Chat { text, origin: Some(o) } => json!({"text": text, "origin": o.origin, "model": o.model.map(|m| hex(&m))}), Payload::Marker(m) => json!({"marker": format!("{m:?}")}), Payload::Reaction(r) => json!({"reaction": format!("{r:?}")}), Payload::Lifecycle { kind: k, subject, detail } => { @@ -535,7 +536,7 @@ pub fn caption_payload(duration_ms: u32, origin: u8, model: Option, lang #[wasm_bindgen(js_name = chatPayload)] pub fn chat_payload(text: String) -> Vec { - Payload::Chat { text }.encode() + Payload::Chat { text, origin: None }.encode() } /// A lifecycle payload (live-rooms §7.2) from its JSON description: `kind` (`join`, diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 6f1192fa2..cf6b21cdb 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -289,6 +289,15 @@ png_still = [] # logs key_log's (key_discovery), its clips int_video's (video_int); jpeg-encoder, already a # dependency of `codecs`, writes its attested JPEG outputs. No new dependency. derive_edge = ["key_discovery", "video_int", "png_still", "dep:jpeg-encoder"] +# Synthesis provenance (extensions/synthesis): `wai.media.synthesis` — the origin claim of an +# object, a group or a track (captured, reconstructed, assistive or synthesized; the class rises +# along a chain of derivations and never falls), its IPTC digital source type and C2PA statement, +# the two pinned integer derivations whose recomputation proves an assistive claim, the marking +# and detection records of the second marking layer and their agreement with the claim, the +# tracks of a room's AI participants, reviews that a player presents only on a decode path of +# the review's tier, and the label obligations of a player's signed log. SHA-256, BLAKE3, +# Ed25519 and the key log only; no new dependency. +synthesis = ["live_room"] # Timed text and live captions (extensions/timed-text): `wai.text.timed` — the cue model, # live caption revisions (`caption` events of wai.live.events with the WTX1 annotation), # and their exact mappings onto WebVTT documents and segments, the IMSC1 Text Profile, @@ -743,6 +752,11 @@ name = "wai_live_moderation_vectors" path = "src/bin/wai_live_moderation_vectors.rs" required-features = ["live_moderation"] +[[bin]] +name = "wai_synthesis_vectors" +path = "src/bin/wai_synthesis_vectors.rs" +required-features = ["synthesis"] + [[bin]] name = "wai_live_watermark_vectors" path = "src/bin/wai_live_watermark_vectors.rs" @@ -1461,6 +1475,11 @@ name = "live_moderation" path = "tests/live_moderation.rs" required-features = ["live_moderation"] +[[test]] +name = "synthesis" +path = "tests/synthesis.rs" +required-features = ["synthesis"] + [[test]] name = "live_watermark" path = "tests/live_watermark.rs" diff --git a/wai/wai-rs/src/bin/wai_synthesis_vectors.rs b/wai/wai-rs/src/bin/wai_synthesis_vectors.rs new file mode 100644 index 000000000..c5b4f06bb --- /dev/null +++ b/wai/wai-rs/src/bin/wai_synthesis_vectors.rs @@ -0,0 +1,74 @@ +//! Conformance corpus for synthesis provenance (`live-conformance/synthesis/`, extensions/synthesis.md §13). +//! +//! wai_synthesis_vectors gen write vectors.json from fixed seeds +//! wai_synthesis_vectors verify every file regenerates byte for byte, no other +//! file but README.md is there, and every case +//! gets the outcome it states +//! +//! The generator and this verifier are one implementation. The corpus's second +//! implementation is `live-conformance/synthesis_verify.py`, written from the extension's +//! text and sharing no code with this binary. + +use std::collections::BTreeMap; +use std::path::Path; +use wai::synthesis::corpus; + +fn main() { + let a: Vec = std::env::args().collect(); + match (a.get(1).map(String::as_str), a.get(2)) { + (Some("gen"), Some(d)) => { + if let Err(e) = std::fs::create_dir_all(d) { + eprintln!("create {d}: {e}"); + std::process::exit(1); + } + for (name, body) in corpus::generate() { + let p = Path::new(d).join(&name); + if let Err(e) = std::fs::write(&p, body) { + eprintln!("write {}: {e}", p.display()); + std::process::exit(1); + } + } + println!("wrote {d}"); + } + (Some("verify"), Some(d)) => std::process::exit(verify(Path::new(d))), + _ => { + eprintln!("usage: wai_synthesis_vectors gen|verify "); + std::process::exit(2); + } + } +} + +fn verify(dir: &Path) -> i32 { + let mut files = BTreeMap::new(); + let entries = match std::fs::read_dir(dir) { + Ok(e) => e, + Err(e) => { + eprintln!("read {}: {e}", dir.display()); + return 1; + } + }; + for e in entries.flatten() { + let name = e.file_name().to_string_lossy().into_owned(); + if name == "README.md" { + continue; + } + match std::fs::read_to_string(e.path()) { + Ok(s) => { + files.insert(name, s); + } + Err(err) => { + eprintln!("read {name}: {err}"); + return 1; + } + } + } + let generated: BTreeMap = corpus::generate().into_iter().collect(); + let same = files == generated; + println!("{} every file regenerates byte for byte from its seeds, and no other file is there", if same { "PASS" } else { "FAIL" }); + let fails = corpus::verify(&files); + for f in &fails { + println!("FAIL {f}"); + } + println!("{} every case gets the outcome the corpus states", if fails.is_empty() { "PASS" } else { "FAIL" }); + (!same || !fails.is_empty()) as i32 +} diff --git a/wai/wai-rs/src/capture_take/corpus.rs b/wai/wai-rs/src/capture_take/corpus.rs index cf11a70bb..3bae5f447 100644 --- a/wai/wai-rs/src/capture_take/corpus.rs +++ b/wai/wai-rs/src/capture_take/corpus.rs @@ -417,7 +417,7 @@ fn write(s: &Scene) -> Written { rm.emit(DIRECTOR, capture + k * S, &Payload::Marker(Marker { taxonomy: 4, polarity, text: note.into(), attachment: None })); } rm.emit(DIRECTOR, s.stop_t, &Payload::Control { target: pk(DEVICE), command: Command::RecordStop }); - let chat = rm.emit(VIEWER, s.start_t + 500 * MS, &Payload::Chat { text: "good luck".into() }); + let chat = rm.emit(VIEWER, s.start_t + 500 * MS, &Payload::Chat { text: "good luck".into(), origin: None }); // The chunks, as captured, as the device commits them, and as the manifest lists them. let device_time = |t: u64| (t as i128 - s.offset as i128) as u64; let bytes: Vec> = (0..s.chunks.len()).map(|i| chunk_bytes(s.label.as_bytes(), i)).collect(); diff --git a/wai/wai-rs/src/codecs/registry.rs b/wai/wai-rs/src/codecs/registry.rs index 2d868e8c9..f7599baa7 100644 --- a/wai/wai-rs/src/codecs/registry.rs +++ b/wai/wai-rs/src/codecs/registry.rs @@ -223,6 +223,7 @@ pub const CAP_LIVE_WATERMARK: &str = "wai.live.watermark"; pub const CAP_LIVE_ADS: &str = "wai.live.ads"; pub const CAP_LIVE_PROVENANCE: &str = "wai.live.provenance"; pub const CAP_EDGE_DERIVATION: &str = "wai.edge.derivation"; +pub const CAP_MEDIA_SYNTHESIS: &str = "wai.media.synthesis"; /// One row per string SPEC §5 registers that is a payload, companion or record: /// `(string, SPEC section, edition, registered)`. @@ -314,6 +315,7 @@ const ROWS: &[(&str, &str, Option<&str>, &str)] = &[ (CAP_LIVE_WATERMARK, S_INTERACTIVE, None, "2026-10-20"), (CAP_LIVE_ADS, S_INTERACTIVE, None, "2026-10-20"), (CAP_LIVE_PROVENANCE, S_INTERACTIVE, None, "2026-10-21"), + (CAP_MEDIA_SYNTHESIS, S_INTERACTIVE, None, "2026-10-21"), (CAP_AUDIO_IAMF, S_IMMERSIVE, None, "2026-06-17"), (CAP_AUDIO_AC4, S_IMMERSIVE, None, "2026-06-17"), (CAP_AUDIO_IVAS, S_IMMERSIVE, None, "2026-06-17"), @@ -368,6 +370,7 @@ const RECORDS: &[&str] = &[ CAP_LIVE_ADS, CAP_LIVE_PROVENANCE, CAP_EDGE_DERIVATION, + CAP_MEDIA_SYNTHESIS, ]; /// A named identifier SPEC uses that has the shape of a capability string but is @@ -526,6 +529,7 @@ const FORENSIC_WATERMARK: &str = "extensions/forensic-watermark.md"; const LIVE_ADS: &str = "extensions/live-ads.md"; const LIVE_PROVENANCE: &str = "extensions/live-provenance.md"; const DERIVING_EDGE: &str = "extensions/deriving-edge.md"; +const SYNTHESIS: &str = "extensions/synthesis.md"; const fn wire(name: &'static str, kind: WireKind, document: &'static str, section: &'static str, status: WireStatus) -> WireIdentifier { @@ -536,9 +540,9 @@ const fn wire(name: &'static str, kind: WireKind, document: &'static str, sectio /// (http-delivery, moq-streaming-format), of the key discovery those bindings serve /// (jwp-receipts §7), of packaging, of live events, of live rooms, of capture binding, of /// live composition, of live recording, of timed text, of live metering, of energy steering, -/// of live moderation, of confidentiality, of forensic watermark sessions, of live ads and of -/// live provenance and of the deriving edge, in document order. The export lists them sorted by -/// name and kind. +/// of live moderation, of confidentiality, of forensic watermark sessions, of live ads, of live +/// provenance, of the deriving edge and of synthesis, in document order. The export lists them +/// sorted by name and kind. pub const WIRE_IDENTIFIERS: &[WireIdentifier] = &[ wire("WAI1", WireKind::Magic, "SPEC.md", "§2", WireStatus::Defined), wire("WAI2", WireKind::Magic, "SPEC.md", "§7.1", WireStatus::Defined), @@ -627,6 +631,12 @@ pub const WIRE_IDENTIFIERS: &[WireIdentifier] = &[ wire("wai_c2pa_track", WireKind::Catalog, LIVE_PROVENANCE, "§10.2", WireStatus::Defined), wire("wai-c2pa", WireKind::Catalog, LIVE_PROVENANCE, "§10.2", WireStatus::Defined), wire("wai-derivation", WireKind::DocumentKind, DERIVING_EDGE, "§5.1", WireStatus::Defined), + wire("wai-origin", WireKind::DocumentKind, SYNTHESIS, "§3.1", WireStatus::Defined), + wire("wai-marking", WireKind::DocumentKind, SYNTHESIS, "§7.2", WireStatus::Defined), + wire("wai-detection", WireKind::DocumentKind, SYNTHESIS, "§7.3", WireStatus::Defined), + wire("wai-review", WireKind::DocumentKind, SYNTHESIS, "§9.1", WireStatus::Defined), + wire("wai-label-policy", WireKind::DocumentKind, SYNTHESIS, "§10.1", WireStatus::Defined), + wire("wai-player", WireKind::DocumentKind, SYNTHESIS, "§10.3", WireStatus::Defined), ]; /// A string with the shape of a capability that the reference implementation uses diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 670040981..36060220d 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -460,6 +460,15 @@ pub mod energy_steering; /// cbindgen:ignore #[cfg(feature = "live_moderation")] pub mod live_moderation; +// Synthesis provenance (extensions/synthesis.md): `wai.media.synthesis` — the origin claim of an +// object, a group or a track and its class (captured, reconstructed, assistive, synthesized), +// its IPTC digital source type and C2PA statement, assistive claims proved by recomputing a pinned +// integer derivation, the two marking layers and their agreement, AI participants' tracks, +// reviews presented only on a decode path of their tier, and a player's label log. Rust-only, +// like the live modules: its constants must not become macros in include/wai.h. +/// cbindgen:ignore +#[cfg(feature = "synthesis")] +pub mod synthesis; // Forensic watermark sessions (extensions/forensic-watermark.md): `wai.live.watermark` — the edge's // session record of the ETSI TS 104 002 A/B variants it embedded, bound to the session's grant by a // salted commitment, the recovered sequence and the rule that matches it, and the room authority's diff --git a/wai/wai-rs/src/live_ads/corpus.rs b/wai/wai-rs/src/live_ads/corpus.rs index dd786e417..8369b6ea1 100644 --- a/wai/wai-rs/src/live_ads/corpus.rs +++ b/wai/wai-rs/src/live_ads/corpus.rs @@ -409,7 +409,7 @@ struct LiveBreak { fn live_break(spec: Spec) -> LiveBreak { let mut room = room(spec); - room.raw(&key(PRODUCER), 10 * S, kind::CHAT, Payload::Chat { text: "kick-off".into() }.encode()); + room.raw(&key(PRODUCER), 10 * S, kind::CHAT, Payload::Chat { text: "kick-off".into(), origin: None }.encode()); let out = room.cue(PRODUCER, 60 * S, cue_type::OUT, 101, 0); let in_ = room.cue(PRODUCER, 90 * S, cue_type::IN, 101, 30_000); let policy = policy(&room, |_| {}); diff --git a/wai/wai-rs/src/live_compose/corpus.rs b/wai/wai-rs/src/live_compose/corpus.rs index 64760a0aa..09a1b3f97 100644 --- a/wai/wai-rs/src/live_compose/corpus.rs +++ b/wai/wai-rs/src/live_compose/corpus.rs @@ -826,10 +826,10 @@ fn studio(v: Variant) -> Written { c.life(S, DIRECTOR, own(media::VIDEO, 2, 2)); c.life(2 * S, MIXER, own(media::VIDEO, 3, 3)); c.life(2 * S, MIXER, own(media::AUDIO, 1, 1)); - c.emit(VIEWER, 2_500 * MS, &Payload::Chat { text: "hello".into() }); + c.emit(VIEWER, 2_500 * MS, &Payload::Chat { text: "hello".into(), origin: None }); c.life(3 * S, GUEST, Detail::Join { grant: gg.digest() }); c.life(3 * S, GUEST, own(media::AUDIO, 0, 1)); - c.emit(VIEWER, 3_500 * MS, &Payload::Chat { text: "welcome".into() }); + c.emit(VIEWER, 3_500 * MS, &Payload::Chat { text: "welcome".into(), origin: None }); c.life(4 * S, SCREEN, Detail::Join { grant: gs.digest() }); c.life(4 * S, SCREEN, own(media::SCREEN, 4, 1)); c.life(4 * S, ENCODER, Detail::Join { grant: ge.digest() }); diff --git a/wai/wai-rs/src/live_events.rs b/wai/wai-rs/src/live_events.rs index 1f7ac92a6..60c1c0a1e 100644 --- a/wai/wai-rs/src/live_events.rs +++ b/wai/wai-rs/src/live_events.rs @@ -446,6 +446,30 @@ pub mod scte_form { pub const EVENT_BASED: u8 = 2; } +/// The origin a `chat` or a `reaction` may state (§4.4, §4.5), as a `caption` states its +/// own (§4.10). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Origin { + /// 1 human, 2 machine, 3 machine-reviewed. + pub origin: u8, + /// The digest naming the model, present exactly when `origin` is 2 or 3. + pub model: Option<[u8; 32]>, +} + +impl Origin { + /// Whether it states that a machine produced the content: origin 2 or 3. + pub fn is_machine(&self) -> bool { + matches!(self.origin, 2 | 3) + } + + fn encode(&self, o: &mut Vec) { + o.push(self.origin); + if let Some(m) = self.model { + o.extend_from_slice(&m); + } + } +} + /// A `reaction` (§4.5). #[derive(Clone, Debug, PartialEq, Eq)] pub struct Reaction { @@ -453,6 +477,8 @@ pub struct Reaction { pub value: u16, pub target: Option<[u8; 32]>, pub remove: bool, + /// The origin it states, if it states one. + pub origin: Option, } /// A `marker` (§4.8). @@ -492,7 +518,8 @@ pub struct Caption { pub enum Payload { Meta(Vec), Cue(Cue), - Chat { text: String }, + /// A message, and the origin it states, if it states one (§4.4). + Chat { text: String, origin: Option }, Reaction(Reaction), PollOpen { question: String, options: Vec }, Vote { poll: [u8; 32], option: u8 }, @@ -549,7 +576,12 @@ impl Payload { } } } - Payload::Chat { text } => put_text(&mut o, text), + Payload::Chat { text, origin } => { + put_text(&mut o, text); + if let Some(g) = origin { + g.encode(&mut o); + } + } Payload::Reaction(r) => { o.push(r.rtype); o.extend_from_slice(&r.value.to_le_bytes()); @@ -557,6 +589,9 @@ impl Payload { if let Some(t) = r.target { o.extend_from_slice(&t); } + if let Some(g) = &r.origin { + g.encode(&mut o); + } } Payload::PollOpen { question, options } => { put_text(&mut o, question); @@ -670,6 +705,22 @@ pub fn splice_info_section_ok(b: &[u8]) -> bool { b.len() >= 3 && b[0] == 0xFC && 3 + ((((b[1] & 0x0F) as usize) << 8) | b[2] as usize) == b.len() } +/// §4.4, §4.5: the optional origin after a `chat`'s or a `reaction`'s last field — `origin` +/// (1, 2 or 3), then `model` for 2 and 3 — or none when no byte follows. +fn read_origin(r: &mut Rd) -> Result, Reason> { + if r.done() { + return Ok(None); + } + let m = Reason::MalformedPayload; + let origin = r.u8().ok_or(m)?; + let model = match origin { + 1 => None, + 2 | 3 => Some(r.d32().ok_or(m)?), + _ => return Err(m), + }; + Ok(Some(Origin { origin, model })) +} + /// Read a payload of kind `k` (§4), in the order its fields are written; each field's own /// rule applies when it is read, and bytes after the last field are `malformed_payload`. /// `k` is a kind this revision defines. @@ -701,7 +752,10 @@ pub fn parse_payload(k: u8, b: &[u8], max_text_cp: u16) -> Result Payload::Chat { text: read_text(&mut r, max_text_cp)? }, + kind::CHAT => { + let text = read_text(&mut r, max_text_cp)?; + Payload::Chat { text, origin: read_origin(&mut r)? } + } kind::REACTION => { let rtype = r.u8().ok_or(m)?; if !(1..=5).contains(&rtype) { @@ -716,7 +770,7 @@ pub fn parse_payload(k: u8, b: &[u8], max_text_cp: u16) -> Result { let question = read_text(&mut r, max_text_cp)?; @@ -1692,7 +1746,7 @@ mod tests { } fn chat(s: &str) -> Payload { - Payload::Chat { text: s.into() } + Payload::Chat { text: s.into(), origin: None } } #[test] @@ -1746,7 +1800,11 @@ mod tests { Payload::Cue(Cue { cue_type: cue_type::BREAK, cue_id: 8, duration_ms: 0, scte: Some((2, vec![1])) }), Payload::Cue(Cue { cue_type: cue_type::CHAPTER, cue_id: 9, duration_ms: 1, scte: None }), chat("héllo"), - Payload::Reaction(Reaction { rtype: 1, value: 3, target: Some(d), remove: true }), + Payload::Chat { text: "hi".into(), origin: Some(Origin { origin: 1, model: None }) }, + Payload::Chat { text: "hi".into(), origin: Some(Origin { origin: 2, model: Some(d) }) }, + Payload::Reaction(Reaction { rtype: 1, value: 3, target: Some(d), remove: true, origin: None }), + Payload::Reaction(Reaction { rtype: 1, value: 3, target: Some(d), remove: false, origin: Some(Origin { origin: 3, model: Some(d) }) }), + Payload::Reaction(Reaction { rtype: 4, value: 1, target: None, remove: false, origin: Some(Origin { origin: 2, model: Some(d) }) }), Payload::PollOpen { question: "q".into(), options: vec!["a".into(), "b".into()] }, Payload::Vote { poll: d, option: 1 }, Payload::PollClose { poll: d, counts: vec![1, 2] }, @@ -1772,6 +1830,22 @@ mod tests { } } + /// §4.4, §4.5: a chat or a reaction states an origin of 1, 2 or 3, with a model exactly + /// for 2 and 3; anything else after its last field is `malformed_payload`. + #[test] + fn a_stated_origin_reads_as_a_captions_does() { + let text = chat("x").encode(); + for (tail, ok) in [(vec![1], true), (vec![2], false), (vec![1, 0], false), (vec![0], false), (vec![4], false)] { + let b = [text.clone(), tail.clone()].concat(); + assert_eq!(parse_payload(kind::CHAT, &b, 500).is_ok(), ok, "{tail:?}"); + } + let b = [text.clone(), vec![3], vec![5; 32]].concat(); + assert_eq!(parse_payload(kind::CHAT, &b, 500), Ok(Payload::Chat { text: "x".into(), origin: Some(Origin { origin: 3, model: Some([5; 32]) }) })); + let r = Payload::Reaction(Reaction { rtype: 4, value: 1, target: None, remove: false, origin: None }).encode(); + assert_eq!(parse_payload(kind::REACTION, &[r.clone(), vec![2]].concat(), 500), Err(Reason::MalformedPayload)); + assert!(parse_payload(kind::REACTION, &[r, vec![2], vec![6; 32]].concat(), 500).is_ok()); + } + #[test] fn text_rules() { let mut b = vec![2, 0, 0xC0, 0xAF]; @@ -1840,7 +1914,7 @@ mod tests { let mut a = Author::new(4); let rs: Vec> = [0, 500_000, 1_000_000].iter().map(|&t| a.emit(t, chat("x")).to_bytes()).collect(); assert_eq!(verify_chain(ROOM, p, &rs), Outcome::Accept); - let r = |target| Payload::Reaction(Reaction { rtype: 1, value: 1, target, remove: false }); + let r = |target| Payload::Reaction(Reaction { rtype: 1, value: 1, target, remove: false, origin: None }); let mut a = Author::new(4); let rs = vec![a.emit(0, r(None)).to_bytes(), a.emit(10, r(Some([1; 32]))).to_bytes(), a.emit(999_999, r(None)).to_bytes()]; assert_eq!(verify_chain(ROOM, Policy::default(), &rs), Outcome::Refuse { index: 2, reason: Reason::RateExceeded }); diff --git a/wai/wai-rs/src/live_events/corpus.rs b/wai/wai-rs/src/live_events/corpus.rs index f1c964351..e246d4d8b 100644 --- a/wai/wai-rs/src/live_events/corpus.rs +++ b/wai/wai-rs/src/live_events/corpus.rs @@ -95,7 +95,7 @@ fn video(group: u64, object: u64) -> Anchor { } fn chat(s: &str) -> Payload { - Payload::Chat { text: s.into() } + Payload::Chat { text: s.into(), origin: None } } fn digest_of(record: &[u8]) -> [u8; 32] { @@ -165,7 +165,7 @@ fn caption(duration_ms: u32, origin: u8, lang: &str, text: &str) -> Payload { } fn reaction(rtype: u8, value: u16, target: Option<[u8; 32]>, remove: bool) -> Payload { - Payload::Reaction(Reaction { rtype, value, target, remove }) + Payload::Reaction(Reaction { rtype, value, target, remove, origin: None }) } fn lifecycle(kind: u8, subject: u8, detail: &[u8]) -> Payload { @@ -333,6 +333,22 @@ fn good_vectors() -> Vec { Intent::Accept, )); + let mut a = Author::new(ALICE); + let model = sha(&[b"wai:live-events/corpus/model/", b"chat"]); + let said = a.at(S, Payload::Chat { text: "Here is a summary of the talk so far.".into(), origin: Some(Origin { origin: 2, model: Some(model) }) }); + let target = digest_of(&said); + out.push(v( + "kind_chat_origin", + "chat and reaction stating their origin (§4.4, §4.5): a machine message with its model's digest, a machine-reviewed one, a human one stating origin 1, and a machine reaction targeting the first", + vec![ + said, + a.at(2 * S, Payload::Chat { text: "Reviewed: the summary above is accurate.".into(), origin: Some(Origin { origin: 3, model: Some(model) }) }), + a.at(3 * S, Payload::Chat { text: "Typed by a person.".into(), origin: Some(Origin { origin: 1, model: None }) }), + a.at(4 * S, Payload::Reaction(Reaction { rtype: 1, value: 1, target: Some(target), remove: false, origin: Some(Origin { origin: 2, model: Some(model) }) })), + ], + Intent::Accept, + )); + let mut h = Author::new(HOST); out.push(v( "kind_caption", @@ -653,6 +669,15 @@ fn negative_vectors() -> Vec { )); let mut h = Author::new(HOST); out.push(v("malformed_payload_lang", "a caption whose language tag is one letter (§4.10)", vec![h.at(S, caption(1000, 1, "e", "x"))], refuse(0, Reason::MalformedPayload))); + let mut a = Author::new(ALICE); + let mut bytes = chat("which model?").encode(); + bytes.push(2); + out.push(v( + "malformed_payload_origin", + "a chat stating origin 2 with no model digest after it (§4.4)", + vec![a.raw(S, None, kind::CHAT, bytes).to_bytes()], + refuse(0, Reason::MalformedPayload), + )); let mut h = Author::new(HOST); out.push(v("malformed_payload_poll", "a poll_open with one option (§4.6)", vec![h.at(S, poll_open("Only one?", &["Yes"]))], refuse(0, Reason::MalformedPayload))); diff --git a/wai/wai-rs/src/live_metering/corpus.rs b/wai/wai-rs/src/live_metering/corpus.rs index 967a47471..d5483cd4a 100644 --- a/wai/wai-rs/src/live_metering/corpus.rs +++ b/wai/wai-rs/src/live_metering/corpus.rs @@ -376,8 +376,8 @@ fn lesson(s: Scene) -> WrittenRoom { for p in &plans { c.emit(DIRECTOR, STOP_T, &Payload::Control { target: pk(p.device), command: Command::RecordStop }); } - c.emit(VIEWER, START_T + 500 * MS, &Payload::Chat { text: "good luck".into() }); - c.emit(VIEWER2, 18 * S, &Payload::Chat { text: "bravo".into() }); + c.emit(VIEWER, START_T + 500 * MS, &Payload::Chat { text: "good luck".into(), origin: None }); + c.emit(VIEWER2, 18 * S, &Payload::Chat { text: "bravo".into(), origin: None }); // Each take: its commitments in the device's chain, and its manifest. let mut takes = Vec::new(); for (k, p) in plans.iter().enumerate() { diff --git a/wai/wai-rs/src/live_moderation/corpus.rs b/wai/wai-rs/src/live_moderation/corpus.rs index 636a79687..ab3ec3859 100644 --- a/wai/wai-rs/src/live_moderation/corpus.rs +++ b/wai/wai-rs/src/live_moderation/corpus.rs @@ -179,11 +179,11 @@ fn role(name: &str, powers: u8, events: &[u8]) -> Role { } fn chat(s: &str) -> Payload { - Payload::Chat { text: s.into() } + Payload::Chat { text: s.into(), origin: None } } fn emoji(target: Option<[u8; 32]>) -> Payload { - Payload::Reaction(Reaction { rtype: 1, value: 1, target, remove: false }) + Payload::Reaction(Reaction { rtype: 1, value: 1, target, remove: false, origin: None }) } /// The chat room: a host and a moderator with the moderate power, two viewers, and the diff --git a/wai/wai-rs/src/live_record/corpus.rs b/wai/wai-rs/src/live_record/corpus.rs index b9ec45733..61841e4bb 100644 --- a/wai/wai-rs/src/live_record/corpus.rs +++ b/wai/wai-rs/src/live_record/corpus.rs @@ -344,7 +344,7 @@ fn write(s: &Scene) -> Written { for t in &s.takes { rm.emit(DIRECTOR, s.stop_t, &Payload::Control { target: pk(t.device), command: Command::RecordStop }); } - rm.emit(VIEWER, s.start_t + 500 * MS, &Payload::Chat { text: "good luck".into() }); + rm.emit(VIEWER, s.start_t + 500 * MS, &Payload::Chat { text: "good luck".into(), origin: None }); // Each take. let mut files = BTreeMap::new(); let mut written = Vec::new(); diff --git a/wai/wai-rs/src/live_room.rs b/wai/wai-rs/src/live_room.rs index 561b5e630..f612617df 100644 --- a/wai/wai-rs/src/live_room.rs +++ b/wai/wai-rs/src/live_room.rs @@ -148,6 +148,7 @@ pub enum Reason { ParticipantLimit, OutsideGrantWindow, PowerNotGranted, + AiUnmarked, PublishNotPermitted, ProfileNotGranted, AlreadyPublished, @@ -179,7 +180,7 @@ impl From for Reason { impl Reason { /// The codes this extension adds, in §11's order. - pub const ALL: [Reason; 42] = [ + pub const ALL: [Reason; 43] = [ Reason::DescriptorInvalid, Reason::KeyLogMissing, Reason::KeyLogNotHeld, @@ -201,6 +202,7 @@ impl Reason { Reason::ParticipantLimit, Reason::OutsideGrantWindow, Reason::PowerNotGranted, + Reason::AiUnmarked, Reason::PublishNotPermitted, Reason::ProfileNotGranted, Reason::AlreadyPublished, @@ -249,6 +251,7 @@ impl Reason { Reason::ParticipantLimit => "participant_limit", Reason::OutsideGrantWindow => "outside_grant_window", Reason::PowerNotGranted => "power_not_granted", + Reason::AiUnmarked => "ai_unmarked", Reason::PublishNotPermitted => "publish_not_permitted", Reason::ProfileNotGranted => "profile_not_granted", Reason::AlreadyPublished => "already_published", @@ -1203,8 +1206,11 @@ impl Grant { if raw.origins.len() > 8 || !raw.origins.iter().all(|o| token_ok(o, 255)) { return bad; } - if read_attrs(&raw.attrs).is_none() { - return bad; + match read_attrs(&raw.attrs) { + None => return bad, + // §4.4: `ai` is a model's digest, as 64 lowercase hexadecimal digits. + Some(a) if a.iter().any(|(k, v)| k == AI_ATTR && hex32(v).is_none()) => return bad, + Some(_) => {} } Ok(()) } @@ -1237,6 +1243,12 @@ impl Grant { Ok(()) } + /// The model an AI participant's grant names in its `ai` attribute (§4.4), or `None` for + /// a grant without one. + pub fn ai_model(&self) -> Option<[u8; 32]> { + self.attrs.iter().find(|(k, _)| k == AI_ATTR).and_then(|(_, v)| hex32(v)) + } + /// The revocation tag of the grant's viewer (§5), when it names one. pub fn viewer_tag(&self) -> Option<[u8; 32]> { (self.viewer != [0; 16]).then(|| sha256(&[VIEWER_DOMAIN, &self.viewer])) @@ -1268,6 +1280,36 @@ fn read_grant(b: &[u8], d: &Descriptor) -> Result<(RawGrant, [u8; 32]), Reason> Ok((raw, sha256(&[body]))) } +/// The attribute that marks an AI participant (§4.4). +pub const AI_ATTR: &str = "ai"; + +/// 64 lowercase hexadecimal digits, read as 32 bytes. +fn hex32(s: &str) -> Option<[u8; 32]> { + let b = s.as_bytes(); + if b.len() != 64 || !b.iter().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(c)) { + return None; + } + let mut o = [0u8; 32]; + for (i, o) in o.iter_mut().enumerate() { + *o = u8::from_str_radix(&s[2 * i..2 * i + 2], 16).ok()?; + } + Some(o) +} + +/// §9.2 step 6: an AI participant's `chat`, `reaction` and `caption` state that a machine +/// produced them, with the model its grant names. Any other payload, or a principal whose +/// grant names no model, is not held to it. +pub fn ai_marked(g: &Grant, p: &Payload) -> bool { + let Some(model) = g.ai_model() else { return true }; + let stated = match p { + Payload::Chat { origin, .. } => origin.map(|o| (o.origin, o.model)), + Payload::Reaction(r) => r.origin.map(|o| (o.origin, o.model)), + Payload::Caption(c) => Some((c.origin, c.model)), + _ => return true, + }; + matches!(stated, Some((2 | 3, Some(m))) if m == model) +} + /// A grant a room verifier holds, with its digest. #[derive(Clone, Debug)] pub struct HeldGrant { @@ -1988,6 +2030,12 @@ fn step(replay: &mut Replay, verifier: &mut RoomVerifier, seen: &mut Seen, b: &[ return Err(Reason::PowerNotGranted); } } + // f. An AI participant's chat, reaction and caption state their machine origin (§4.4). + if let Some(a) = verifier.accepted().last() + && !ai_marked(&h.grant, &a.payload) + { + return Err(Reason::AiUnmarked); + } Ok(Some(h.digest)) } diff --git a/wai/wai-rs/src/live_room/corpus.rs b/wai/wai-rs/src/live_room/corpus.rs index 018b2d3c5..305392970 100644 --- a/wai/wai-rs/src/live_room/corpus.rs +++ b/wai/wai-rs/src/live_room/corpus.rs @@ -301,7 +301,7 @@ impl Build { } fn chat(s: &str) -> Payload { - Payload::Chat { text: s.into() } + Payload::Chat { text: s.into(), origin: None } } fn control(target: u8, command: Command) -> Payload { @@ -372,7 +372,7 @@ fn lesson() -> (Build, [u8; 32]) { b.ev(PERFORMER, 8 * S, take_commit(1)); b.ev(DIRECTOR, 9 * S, Payload::Marker(Marker { taxonomy: 2, polarity: 2, text: "rushed bar 12".into(), attachment: None })); b.ev(DIRECTOR, 10 * S, control(PERFORMER, Command::RecordStop)); - b.ev(VIEWER_B, 10_500_000, Payload::Reaction(Reaction { rtype: 4, value: 1, target: None, remove: false })); + b.ev(VIEWER_B, 10_500_000, Payload::Reaction(Reaction { rtype: 4, value: 1, target: None, remove: false, origin: None })); b.ev(DIRECTOR, 11 * S, chat("again from bar 9")); b.life(12 * S, VIEWER_B, Detail::Leave { reason: leave::LEFT }); (b, revoked) @@ -408,7 +408,7 @@ fn broadcast() -> Build { b.ev(VIEWER_C, 4_200_000, Payload::Vote { poll, option: 0 }); let spam = b.ev(VIEWER_C, 5 * S, chat("first!!! follow the link in my name")); b.ev(MODERATOR, 5_500_000, Payload::Decision { verdict: 2, target: spam, reason: 3 }); - b.ev(VIEWER_A, 6 * S, Payload::Reaction(Reaction { rtype: 4, value: 1, target: None, remove: false })); + b.ev(VIEWER_A, 6 * S, Payload::Reaction(Reaction { rtype: 4, value: 1, target: None, remove: false, origin: None })); b.ev(HOST, 7 * S, Payload::PollClose { poll, counts: vec![1, 2] }); b.life(8 * S, VIEWER_C, Detail::Leave { reason: leave::REMOVED }); b @@ -483,7 +483,7 @@ fn dvr() -> Build { b.join(S, VIEWER_A, da); b.join(2 * S, VIEWER_B, db); b.ev(VIEWER_A, 3 * S, chat("going back to the opening")); - b.ev(VIEWER_B, 4 * S, Payload::Reaction(Reaction { rtype: 4, value: 1, target: None, remove: false })); + b.ev(VIEWER_B, 4 * S, Payload::Reaction(Reaction { rtype: 4, value: 1, target: None, remove: false, origin: None })); b } @@ -527,7 +527,7 @@ fn good_vectors() -> Vec { b.publish(11 * S, VIEWER_A, "", media::AUDIO, 0, 1); b.publish(11 * S, VIEWER_A, "", media::VIDEO, 1, 2); b.ev(VIEWER_A, 12 * S, Payload::Marker(Marker { taxonomy: 1, polarity: 1, text: "question".into(), attachment: None })); - b.ev(VIEWER_B, 12 * S, Payload::Reaction(Reaction { rtype: 4, value: 1, target: None, remove: false })); + b.ev(VIEWER_B, 12 * S, Payload::Reaction(Reaction { rtype: 4, value: 1, target: None, remove: false, origin: None })); out.push(vector( "promote_viewer", "a viewer's audience grant exchanged at the 10 s tick for a guest grant chained to it: before, it may chat only; after, it publishes audio and video and sends a marker (§4.6)", @@ -711,6 +711,12 @@ fn grant_vectors() -> Vec { &one(&|b| Grant { policy: Policy::with_kinds(&[kind::CHAT, kind::JOIN]), ..b.grant(VIEWER_A, 2) }.sign(&key(AUTHORITY)).to_bytes()), refuse(1, Reason::GrantInvalid), )); + out.push(vector( + "grant_ai_malformed", + "a grant whose ai attribute is not 64 lowercase hexadecimal digits (§4.4)", + &one(&|b| Grant { attrs: vec![("ai".into(), "A".repeat(64))], ..b.grant(VIEWER_A, 2) }.sign(&key(AUTHORITY)).to_bytes()), + refuse(1, Reason::GrantInvalid), + )); out.push(vector( "grant_attrs_too_long", "a grant whose attributes take more than 1024 bytes (§4.4)", @@ -1008,6 +1014,38 @@ fn record_vectors() -> Vec { let r = refuse_last(&b, HOST, Reason::PowerNotGranted); out.push(vector("power_not_granted", "a host with the direct power but not record mutes a viewer, then sends record_start (§3.3)", &b, r)); + // An AI participant (§4.4): a host whose grant names the model that acts for it. + let model = sha(&[b"wai:live-rooms/corpus/model"]); + let ai_room = |says: Option| { + let mut b = Build::new(base()); + let g = Grant { attrs: vec![("ai".into(), hex(&model))], ..b.grant(HOST, 0) }; + let gv = b.grant(VIEWER_A, 2); + let (d, dv) = (b.add(g), b.add(gv)); + b.join(0, HOST, d); + b.join(0, VIEWER_A, dv); + b.ev(VIEWER_A, S, Payload::Chat { text: "Is anyone there?".into(), origin: None }); + b.ev(HOST, 2 * S, Payload::Chat { text: "Welcome. I am an automated host.".into(), origin: says }); + b + }; + let mut b = ai_room(Some(events::Origin { origin: 2, model: Some(model) })); + b.ev(HOST, 3 * S, Payload::Caption(events::Caption { duration_ms: 2_000, origin: 3, model: Some(model), lang: "en".into(), text: "Welcome.".into() })); + b.ev(VIEWER_A, 4 * S, Payload::Reaction(events::Reaction { rtype: 4, value: 1, target: None, remove: false, origin: None })); + out.push(vector( + "ai_participant", + "a host whose grant's ai attribute names a model: its chat states origin 2 and its caption origin 3, each with that model; a person's chat and reaction state none (§4.4, §9.2 step 6)", + &b, + Intent::Accept, + )); + let b = ai_room(None); + let r = refuse_last(&b, HOST, Reason::AiUnmarked); + out.push(vector("ai_unmarked", "an AI participant's chat that states no origin (§9.2 step 6)", &b, r)); + let b = ai_room(Some(events::Origin { origin: 1, model: None })); + let r = refuse_last(&b, HOST, Reason::AiUnmarked); + out.push(vector("ai_unmarked_human", "an AI participant's chat that states origin 1, human (§9.2 step 6)", &b, r)); + let b = ai_room(Some(events::Origin { origin: 2, model: Some(sha(&[b"wai:live-rooms/corpus/other-model"])) })); + let r = refuse_last(&b, HOST, Reason::AiUnmarked); + out.push(vector("ai_unmarked_model", "an AI participant's chat that states origin 2 with a model other than its grant's (§9.2 step 6)", &b, r)); + let mut b = Build::new(base()); let g = b.grant(VIEWER_A, 2); let g = Grant { policy: Policy { max_events: 2, ..g.policy }, ..g }; diff --git a/wai/wai-rs/src/synthesis.rs b/wai/wai-rs/src/synthesis.rs new file mode 100644 index 000000000..d58099cab --- /dev/null +++ b/wai/wai-rs/src/synthesis.rs @@ -0,0 +1,1403 @@ +//! Synthesis provenance (`extensions/synthesis.md`): the record `wai.media.synthesis`. +//! +//! An **origin claim** (`wai-origin`) says of one object, one group or one track what it +//! is: `captured`, `reconstructed` (a codec, neural or not, rebuilding what was captured), +//! `assistive` (an edit that does not substantially alter its input) or `synthesized` +//! (generated). Its class is the greater of the class of the step that made it and the +//! classes of its inputs, each named by the link hash of its own claim, so the class rises +//! along a chain of derivations and never falls (§3). It restates the content's IPTC +//! digital source type, which is a function of the claim (§5), and so is its C2PA statement: +//! the actions and the `c2pa.ai-disclosure` assertions a manifest carries (§6). An assistive +//! claim carries **evidence**: where the step is one of the pinned integer derivations of §4, +//! a verifier that holds the input's decoded samples recomputes the output and compares it, +//! so the claim of assistiveness is checked rather than trusted; any other assistive step can +//! only assert it. +//! +//! Dual-layer marking (§7): the signed claim is the first layer. The second is a watermark, +//! named by a **marking** record (`wai-marking`) whose payload is a function of the claim, +//! and read back by a **detection** record (`wai-detection`); a verifier refuses a detection +//! whose payload disagrees with the claim. This revision registers no watermark algorithm: +//! the scheme is a slot the later slice SYN-2 fills. +//! +//! AI participants of rooms (§8): live-rooms §4.4 and §9.2 step 6 mark them and hold their +//! events to it; this module adds that every track such a participant publishes has a track +//! claim of class `synthesized` naming its model. +//! +//! Certified synthesis (§9): a **review** (`wai-review`) is a signed verdict over one +//! reconstruction at a determinism tier, and a player presents it only for a presentation +//! whose decode path holds that tier and whose output is the reconstruction reviewed. +//! +//! Label display (§10): a **label policy** (`wai-label-policy`) says which classes are +//! labelled and with which asset, and a player's signed **log** (`wai-player`) of what it +//! presented, what label it showed and when presentation resumed after an interruption is +//! held to it: at first exposure, after each interruption, and at the policy's interval. +//! +//! The corpus is `live-conformance/synthesis/` ([`corpus`]); its second implementation is +//! `live-conformance/synthesis_verify.py`. + +use std::collections::{BTreeMap, BTreeSet}; + +use ed25519_dalek::{Signer, SigningKey}; +use serde_json::{json, Map, Value}; +use sha2::{Digest, Sha256}; + +use crate::claim_receipt::{rfc3339, term_ok}; +use crate::jcs::{jcs, MAX_INTEGER}; +use crate::live_events::{Event, Payload}; +use crate::live_room::{self as room, Detail}; + +pub mod corpus; + +/// The domain every record of this extension is signed under (§2). +pub const SIGN_DOMAIN: &[u8] = b"wai:synthesis\x01"; +/// The domain of a record's link hash (§2). +pub const LINK_DOMAIN: &[u8] = b"wai:synthesis-link\x01"; +/// The IPTC Digital Source Type vocabulary, whose codes §5 uses. +pub const IPTC: &str = "http://cv.iptc.org/newscodes/digitalsourcetype/"; +/// The prefix of a model's identifier in an AI disclosure (§6), timed-text §10.3's. +pub const MODEL_ID: &str = "tag:transaction.science,2026:wai/model/"; +/// The pinned integer derivations of §4. +pub const UPSCALE2: &str = "wai-synthesis:upscale2"; +pub const DENOISE3: &str = "wai-synthesis:denoise3"; +/// The most bytes a derivation's samples hold (§4). +pub const MAX_SAMPLES: u64 = 1 << 26; +/// The most inputs and models a claim names (§3.1). +pub const MAX_LIST: usize = 16; + +/// Why a case is refused (§12). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Reason { + RecordMalformed, + BadKey, + BadSignature, + ClaimConflict, + ClassMismatch, + SourceMismatch, + InputMismatch, + ClassUnderstated, + EvidenceInvalid, + InputSamplesMismatch, + AssistiveRecomputeMismatch, + SamplesMismatch, + AssistiveNotProved, + ClaimUnknown, + MarkingMismatch, + DetectionMismatch, + WatermarkAbsent, + LayersDisagree, + LayerMissing, + AiTrackUnlabelled, + PlayerChainBroken, + PresentMismatch, + PresentOutputIrreproducible, + ReviewSubjectMismatch, + ReviewRejected, + ReviewPathNotExact, + ReviewReconstructionMismatch, + LabelAssetMismatch, + LabelNotShown, + /// A room verifier's code (live-rooms §11, live-events §9), raised at the room. + Room(room::Reason), +} + +impl Reason { + /// The codes this extension adds, in §12's order. + pub const ALL: [Reason; 29] = [ + Reason::RecordMalformed, + Reason::BadKey, + Reason::BadSignature, + Reason::ClaimConflict, + Reason::ClassMismatch, + Reason::SourceMismatch, + Reason::InputMismatch, + Reason::ClassUnderstated, + Reason::EvidenceInvalid, + Reason::InputSamplesMismatch, + Reason::AssistiveRecomputeMismatch, + Reason::SamplesMismatch, + Reason::AssistiveNotProved, + Reason::ClaimUnknown, + Reason::MarkingMismatch, + Reason::DetectionMismatch, + Reason::WatermarkAbsent, + Reason::LayersDisagree, + Reason::LayerMissing, + Reason::AiTrackUnlabelled, + Reason::PlayerChainBroken, + Reason::PresentMismatch, + Reason::PresentOutputIrreproducible, + Reason::ReviewSubjectMismatch, + Reason::ReviewRejected, + Reason::ReviewPathNotExact, + Reason::ReviewReconstructionMismatch, + Reason::LabelAssetMismatch, + Reason::LabelNotShown, + ]; + + /// The conformance code. + pub fn code(self) -> &'static str { + match self { + Reason::RecordMalformed => "record_malformed", + Reason::BadKey => "bad_key", + Reason::BadSignature => "bad_signature", + Reason::ClaimConflict => "claim_conflict", + Reason::ClassMismatch => "class_mismatch", + Reason::SourceMismatch => "source_mismatch", + Reason::InputMismatch => "input_mismatch", + Reason::ClassUnderstated => "class_understated", + Reason::EvidenceInvalid => "evidence_invalid", + Reason::InputSamplesMismatch => "input_samples_mismatch", + Reason::AssistiveRecomputeMismatch => "assistive_recompute_mismatch", + Reason::SamplesMismatch => "samples_mismatch", + Reason::AssistiveNotProved => "assistive_not_proved", + Reason::ClaimUnknown => "claim_unknown", + Reason::MarkingMismatch => "marking_mismatch", + Reason::DetectionMismatch => "detection_mismatch", + Reason::WatermarkAbsent => "watermark_absent", + Reason::LayersDisagree => "layers_disagree", + Reason::LayerMissing => "layer_missing", + Reason::AiTrackUnlabelled => "ai_track_unlabelled", + Reason::PlayerChainBroken => "player_chain_broken", + Reason::PresentMismatch => "present_mismatch", + Reason::PresentOutputIrreproducible => "present_output_irreproducible", + Reason::ReviewSubjectMismatch => "review_subject_mismatch", + Reason::ReviewRejected => "review_rejected", + Reason::ReviewPathNotExact => "review_path_not_exact", + Reason::ReviewReconstructionMismatch => "review_reconstruction_mismatch", + Reason::LabelAssetMismatch => "label_asset_mismatch", + Reason::LabelNotShown => "label_not_shown", + Reason::Room(r) => r.code(), + } + } +} + +impl std::fmt::Display for Reason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.code()) + } +} + +impl std::error::Error for Reason {} + +// --------------------------------------------------------------------------------------- +// §3 Classes and steps +// --------------------------------------------------------------------------------------- + +/// The four classes, in the lattice's order (§3.1). +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Class { + Captured, + Reconstructed, + Assistive, + Synthesized, +} + +impl Class { + pub const ALL: [Class; 4] = [Class::Captured, Class::Reconstructed, Class::Assistive, Class::Synthesized]; + + pub fn name(self) -> &'static str { + match self { + Class::Captured => "captured", + Class::Reconstructed => "reconstructed", + Class::Assistive => "assistive", + Class::Synthesized => "synthesized", + } + } + + pub fn from_name(s: &str) -> Option { + Class::ALL.into_iter().find(|c| c.name() == s) + } + + /// The first byte of a watermark's payload (§7.2). + pub fn code(self) -> u8 { + self as u8 + } +} + +/// What the claim's signer did to make its subject (§3.2). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Step { + Capture, + Convert, + Assist, + Generate, +} + +impl Step { + pub const ALL: [Step; 4] = [Step::Capture, Step::Convert, Step::Assist, Step::Generate]; + + pub fn name(self) -> &'static str { + match self { + Step::Capture => "capture", + Step::Convert => "convert", + Step::Assist => "assist", + Step::Generate => "generate", + } + } + + pub fn from_name(s: &str) -> Option { + Step::ALL.into_iter().find(|c| c.name() == s) + } + + /// The class a step gives its output on its own (§3.2). + pub fn class(self) -> Class { + match self { + Step::Capture => Class::Captured, + Step::Convert => Class::Reconstructed, + Step::Assist => Class::Assistive, + Step::Generate => Class::Synthesized, + } + } +} + +/// The digital source types a claim may state (§5): those a capture states, then the rest. +pub const CAPTURE_SOURCES: [&str; 4] = ["digitalCapture", "computationalCapture", "screenCapture", "digitalCreation"]; +pub const SOURCES: [&str; 11] = [ + "digitalCapture", + "computationalCapture", + "screenCapture", + "digitalCreation", + "algorithmicallyEnhanced", + "trainedAlgorithmicMedia", + "compositeWithTrainedAlgorithmicMedia", + "algorithmicMedia", + "composite", + "compositeCapture", + "compositeSynthetic", +]; +/// The oversight levels of C2PA 2.4 §18.28.4. +pub const OVERSIGHT: [&str; 3] = ["fully_autonomous", "prompt_guided", "human_validated"]; +pub const MEDIA: [&str; 4] = ["audio", "video", "image", "text"]; + +// --------------------------------------------------------------------------------------- +// §2 Records: encodings, signature, identity +// --------------------------------------------------------------------------------------- + +fn sha256(parts: &[&[u8]]) -> [u8; 32] { + let mut h = Sha256::new(); + for p in parts { + h.update(p); + } + h.finalize().into() +} + +pub fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +fn unhex(s: &str) -> Option> { + if !s.len().is_multiple_of(2) || !s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) { + return None; + } + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok()).collect() +} + +fn hexn(v: &Value) -> Option<[u8; N]> { + unhex(v.as_str()?)?.try_into().ok() +} + +fn int(v: &Value) -> Option { + v.as_u64().filter(|&n| n <= MAX_INTEGER) +} + +fn term(v: &Value) -> Option { + let s = v.as_str()?; + term_ok(s).ok()?; + Some(s.to_string()) +} + +fn one_of(v: &Value, set: &[&str]) -> Option { + let s = v.as_str()?; + set.contains(&s).then(|| s.to_string()) +} + +/// A value the encodings admit (§2): strings, integers, `null`, arrays and objects of them. +fn subset_ok(v: &Value) -> bool { + match v { + Value::Null | Value::String(_) => true, + Value::Number(_) => int(v).is_some(), + Value::Bool(_) => false, + Value::Array(a) => a.iter().all(subset_ok), + Value::Object(o) => o.values().all(subset_ok), + } +} + +/// An object with exactly these members, or `None`. +fn exact<'a>(v: &'a Value, members: &[&str]) -> Option<&'a Map> { + let o = v.as_object()?; + (o.len() == members.len() && members.iter().all(|m| o.contains_key(*m))).then_some(o) +} + +fn nullable(v: &Value, f: impl Fn(&Value) -> Option) -> Option> { + if v.is_null() { Some(None) } else { f(v).map(Some) } +} + +/// The bytes a record is signed over (§2): the domain, then JCS of the record without `sig`. +pub fn signing_bytes(record: &Value) -> Vec { + let mut r = record.clone(); + if let Some(o) = r.as_object_mut() { + o.remove("sig"); + } + let mut m = SIGN_DOMAIN.to_vec(); + m.extend_from_slice(jcs(&r).expect("a record in the subset").as_bytes()); + m +} + +/// A record's link hash (§2): BLAKE3 over the domain and JCS of the whole record. +pub fn link_hash(record: &Value) -> [u8; 32] { + let mut h = blake3::Hasher::new(); + h.update(LINK_DOMAIN); + h.update(jcs(record).expect("a record in the subset").as_bytes()); + *h.finalize().as_bytes() +} + +/// Sign a record body: adds `signer_pubkey` and `sig` (§2). +pub fn sign(mut body: Value, key: &SigningKey) -> Value { + let o = body.as_object_mut().expect("a record is an object"); + o.insert("signer_pubkey".into(), json!(hex(&key.verifying_key().to_bytes()))); + o.remove("sig"); + let sig = key.sign(&signing_bytes(&body)); + body.as_object_mut().expect("an object").insert("sig".into(), json!(hex(&sig.to_bytes()))); + body +} + +/// Read one record's text (§2): strict JSON, an object of `kind`'s exact members, then the +/// signature. Returns the value, its signer and its link hash. +fn read_record(text: &str, kind: &str, members: &[&str]) -> Result<(Value, [u8; 32], [u8; 32]), Reason> { + let v = crate::claim_text::read(text).map_err(|_| Reason::RecordMalformed)?; + let o = exact(&v, members).ok_or(Reason::RecordMalformed)?; + if o.get("kind").and_then(Value::as_str) != Some(kind) || !subset_ok(&v) { + return Err(Reason::RecordMalformed); + } + let signer: [u8; 32] = hexn(&o["signer_pubkey"]).ok_or(Reason::RecordMalformed)?; + if hexn::<64>(&o["sig"]).is_none() { + return Err(Reason::RecordMalformed); + } + if let Some(at) = o.get("issued_at") + && !at.as_str().is_some_and(rfc3339) + { + return Err(Reason::RecordMalformed); + } + let link = link_hash(&v); + Ok((v, signer, link)) +} + +fn check_signature(v: &Value, signer: &[u8; 32]) -> Result<(), Reason> { + let sig: [u8; 64] = hexn(&v["sig"]).ok_or(Reason::RecordMalformed)?; + crate::sig::verify_strict(signer, &signing_bytes(v), &sig).map_err(|e| match e { + crate::sig::SigError::BadKey => Reason::BadKey, + crate::sig::SigError::BadSignature => Reason::BadSignature, + }) +} + +// --------------------------------------------------------------------------------------- +// §3–§4 The origin claim +// --------------------------------------------------------------------------------------- + +/// What a claim is about (§3.1). +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Subject { + /// An object, by its content hash (jwp-receipts §2.1). + Object([u8; 32]), + /// A group, by its group receipt's link hash (jwp-receipts §3 step 3). + Group([u8; 32]), + /// A track from one group on, by the track's digest (live-rooms §6.2). + Track { track: [u8; 32], from_group: u64 }, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Model { + pub digest: [u8; 32], + pub trained: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Input { + pub content_hash: [u8; 32], + pub origin: Option<[u8; 32]>, + pub class: Class, + pub source: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Evidence { + pub replicate: bool, + pub derivation: String, + pub params: Value, + pub width: u64, + pub height: u64, + pub input_samples: [u8; 32], + pub output_samples: [u8; 32], +} + +/// An origin claim, read (§3.1). +#[derive(Clone, Debug)] +pub struct Origin { + pub value: Value, + pub link: [u8; 32], + pub signer: [u8; 32], + pub subject: Subject, + pub media: String, + pub class: Class, + pub step: Step, + pub operation: String, + pub record: Option<[u8; 32]>, + pub source: String, + pub models: Vec, + pub oversight: Option, + pub inputs: Vec, + pub evidence: Option, +} + +pub const ORIGIN_MEMBERS: [&str; 15] = [ + "kind", "subject", "media", "class", "step", "operation", "record", "source", "models", "oversight", "inputs", "evidence", "issued_at", + "signer_pubkey", "sig", +]; + +fn read_subject(v: &Value) -> Option { + match v.get("type")?.as_str()? { + "object" => Some(Subject::Object(hexn(&exact(v, &["type", "content_hash"])?["content_hash"])?)), + "group" => Some(Subject::Group(hexn(&exact(v, &["type", "receipt"])?["receipt"])?)), + "track" => { + let o = exact(v, &["type", "track", "from_group"])?; + Some(Subject::Track { track: hexn(&o["track"])?, from_group: int(&o["from_group"])? }) + } + _ => None, + } +} + +fn read_evidence(v: &Value) -> Option { + let o = exact(v, &["tier", "derivation", "params", "width", "height", "input_samples", "output_samples"])?; + let replicate = match o["tier"].as_str()? { + "replicate" => true, + "attest" => false, + _ => return None, + }; + let (width, height) = (int(&o["width"])?, int(&o["height"])?); + if width == 0 || height == 0 || !o["params"].is_object() { + return None; + } + Some(Evidence { + replicate, + derivation: term(&o["derivation"])?, + params: o["params"].clone(), + width, + height, + input_samples: hexn(&o["input_samples"])?, + output_samples: hexn(&o["output_samples"])?, + }) +} + +/// §3.1's reading: every member of its type and range, and §3.2's constraints of the step. +fn read_origin_fields(v: &Value, link: [u8; 32], signer: [u8; 32]) -> Option { + let o = v.as_object()?; + let models: Vec = o["models"] + .as_array()? + .iter() + .map(|m| { + let m = exact(m, &["digest", "type"])?; + let trained = match m["type"].as_str()? { + "trained" => true, + "algorithmic" => false, + _ => return None, + }; + Some(Model { digest: hexn(&m["digest"])?, trained }) + }) + .collect::>()?; + let inputs: Vec = o["inputs"] + .as_array()? + .iter() + .map(|i| { + let i = exact(i, &["content_hash", "origin", "class", "source"])?; + Some(Input { + content_hash: hexn(&i["content_hash"])?, + origin: nullable(&i["origin"], hexn)?, + class: Class::from_name(i["class"].as_str()?)?, + source: one_of(&i["source"], &SOURCES)?, + }) + }) + .collect::>()?; + let c = Origin { + value: v.clone(), + link, + signer, + subject: read_subject(&o["subject"])?, + media: one_of(&o["media"], &MEDIA)?, + class: Class::from_name(o["class"].as_str()?)?, + step: Step::from_name(o["step"].as_str()?)?, + operation: term(&o["operation"])?, + record: nullable(&o["record"], hexn)?, + source: one_of(&o["source"], &SOURCES)?, + oversight: nullable(&o["oversight"], |x| one_of(x, &OVERSIGHT))?, + evidence: nullable(&o["evidence"], read_evidence)?, + models, + inputs, + }; + let distinct_models = c.models.iter().map(|m| m.digest).collect::>().len() == c.models.len(); + let distinct_inputs = c.inputs.iter().map(|i| i.content_hash).collect::>().len() == c.inputs.len(); + if c.models.len() > MAX_LIST || c.inputs.len() > MAX_LIST || !distinct_models || !distinct_inputs { + return None; + } + let (n, m) = (c.inputs.len(), c.models.len()); + let ok = match c.step { + Step::Capture => n == 0 && m == 0 && c.oversight.is_none() && c.evidence.is_none() && CAPTURE_SOURCES.contains(&c.source.as_str()), + Step::Convert => n >= 1 && m == 0 && c.oversight.is_none() && c.evidence.is_none(), + Step::Assist => n == 1 && c.oversight.is_none() && c.evidence.is_some(), + Step::Generate => m >= 1 && c.oversight.is_some() && c.evidence.is_none(), + }; + ok.then_some(c) +} + +/// Read an origin claim's text (§3.1, §2). +pub fn read_origin(text: &str) -> Result { + let (v, signer, link) = read_record(text, "wai-origin", &ORIGIN_MEMBERS)?; + let c = read_origin_fields(&v, link, signer).ok_or(Reason::RecordMalformed)?; + check_signature(&v, &signer)?; + Ok(c) +} + +impl Origin { + /// §3.2: the greater of the step's class and its inputs' classes. + pub fn composed_class(&self) -> Class { + self.inputs.iter().map(|i| i.class).fold(self.step.class(), Ord::max) + } + + /// §5: the source a generation gives its output. + pub fn generated_source(&self) -> &'static str { + if !self.models.iter().any(|m| m.trained) { + "algorithmicMedia" + } else if self.oversight.as_deref() == Some("human_validated") || self.inputs.iter().any(|i| i.class != Class::Synthesized) { + "compositeWithTrainedAlgorithmicMedia" + } else { + "trainedAlgorithmicMedia" + } + } + + /// §5: the digital source type of the subject, a function of the claim. + pub fn computed_source(&self) -> String { + match self.step { + Step::Capture => self.source.clone(), + Step::Convert if self.inputs.len() == 1 => self.inputs[0].source.clone(), + Step::Convert => { + if self.inputs.iter().any(|i| i.class == Class::Synthesized) { + "compositeSynthetic".into() + } else if self.inputs.iter().all(|i| matches!(i.source.as_str(), "digitalCapture" | "computationalCapture")) { + "compositeCapture".into() + } else { + "composite".into() + } + } + Step::Assist if self.inputs[0].class == Class::Synthesized => self.inputs[0].source.clone(), + Step::Assist => "algorithmicallyEnhanced".into(), + Step::Generate => self.generated_source().into(), + } + } + + /// §6: the C2PA statement, `{"actions": [...], "assertions": [...]}`, as a JSON value. + pub fn c2pa_statement(&self) -> Value { + let dst = |code: &str| format!("{IPTC}{code}"); + let actions = match (self.inputs.len(), self.step) { + (1, Step::Convert) => json!([{"action": "c2pa.opened"}, {"action": "c2pa.converted"}]), + (1, Step::Assist) => json!([{"action": "c2pa.opened"}, {"action": "c2pa.edited", "digitalSourceType": dst("algorithmicallyEnhanced")}]), + (1, Step::Generate) => json!([{"action": "c2pa.opened"}, {"action": "c2pa.edited", "digitalSourceType": dst(self.generated_source())}]), + _ => json!([{"action": "c2pa.created", "digitalSourceType": dst(&self.source)}]), + }; + let assertions: Vec = self + .models + .iter() + .filter(|m| m.trained) + .enumerate() + .map(|(i, m)| { + let label = if i == 0 { "c2pa.ai-disclosure".to_string() } else { format!("c2pa.ai-disclosure__{i}") }; + let mut data = json!({"modelType": "c2pa.types.model", "modelIdentifier": format!("{MODEL_ID}{}", hex(&m.digest))}); + if self.step == Step::Generate { + data["contentProfile"] = json!({"humanOversightLevel": self.oversight.clone()}); + } + json!({"label": label, "data": data}) + }) + .collect(); + json!({"actions": actions, "assertions": assertions}) + } + + /// §6: the statement written as its JCS text. + pub fn export(&self) -> String { + jcs(&self.c2pa_statement()).expect("a statement in the subset") + } + + /// §7.2: the payload a watermark carrying this claim holds: the class's code, then the + /// first seven bytes of the claim's link hash. + pub fn mark_payload(&self) -> [u8; 8] { + let mut p = [0u8; 8]; + p[0] = self.class.code(); + p[1..].copy_from_slice(&self.link[..7]); + p + } +} + +// --------------------------------------------------------------------------------------- +// §4 The pinned integer derivations +// --------------------------------------------------------------------------------------- + +/// `wai-synthesis:upscale2` (§4.1): a `w` × `h` RGB8 buffer to `2w` × `2h`, each new sample +/// the rounded mean of its neighbours, the last column and row repeated. +pub fn upscale2(input: &[u8], w: usize, h: usize) -> Vec { + let at = |x: usize, y: usize, c: usize| input[(y * w + x) * 3 + c] as u32; + let ow = 2 * w; + let mut out = vec![0u8; 4 * w * h * 3]; + for y in 0..h { + for x in 0..w { + let (x1, y1) = ((x + 1).min(w - 1), (y + 1).min(h - 1)); + for c in 0..3 { + let (a, b, cc, d) = (at(x, y, c), at(x1, y, c), at(x, y1, c), at(x1, y1, c)); + out[((2 * y) * ow + 2 * x) * 3 + c] = a as u8; + out[((2 * y) * ow + 2 * x + 1) * 3 + c] = ((a + b + 1) >> 1) as u8; + out[((2 * y + 1) * ow + 2 * x) * 3 + c] = ((a + cc + 1) >> 1) as u8; + out[((2 * y + 1) * ow + 2 * x + 1) * 3 + c] = ((a + b + cc + d + 2) >> 2) as u8; + } + } + } + out +} + +/// `wai-synthesis:denoise3` (§4.2): each sample the 3 × 3 binomial mean of its +/// neighbourhood, weights 1 2 1 / 2 4 2 / 1 2 1, edges clamped, rounded by adding 8 before +/// the shift by 4. +pub fn denoise3(input: &[u8], w: usize, h: usize) -> Vec { + const K: [[u32; 3]; 3] = [[1, 2, 1], [2, 4, 2], [1, 2, 1]]; + let mut out = vec![0u8; w * h * 3]; + for y in 0..h { + for x in 0..w { + for c in 0..3 { + let mut s = 8u32; + for (j, row) in K.iter().enumerate() { + for (i, k) in row.iter().enumerate() { + let xx = (x + i).saturating_sub(1).min(w - 1); + let yy = (y + j).saturating_sub(1).min(h - 1); + s += k * input[(yy * w + xx) * 3 + c] as u32; + } + } + out[(y * w + x) * 3 + c] = (s >> 4) as u8; + } + } + } + out +} + +/// §4.3: whether replicate evidence names a pinned derivation with its parameters and a +/// size within [`MAX_SAMPLES`]. +fn replicate_evidence_ok(e: &Evidence) -> bool { + let factor = match e.derivation.as_str() { + UPSCALE2 => 4, + DENOISE3 => 1, + _ => return false, + }; + let empty = e.params.as_object().is_some_and(Map::is_empty); + let size = e.width.checked_mul(e.height).and_then(|n| n.checked_mul(3 * factor)); + empty && size.is_some_and(|n| n <= MAX_SAMPLES) +} + +/// Run a pinned derivation over a buffer the evidence sizes. +pub fn derive(derivation: &str, input: &[u8], w: usize, h: usize) -> Option> { + match derivation { + UPSCALE2 => Some(upscale2(input, w, h)), + DENOISE3 => Some(denoise3(input, w, h)), + _ => None, + } +} + +// --------------------------------------------------------------------------------------- +// §7 Marking and detection +// --------------------------------------------------------------------------------------- + +pub const MARKING_MEMBERS: [&str; 9] = ["kind", "subject", "origin", "scheme", "scheme_digest", "payload", "issued_at", "signer_pubkey", "sig"]; +pub const DETECTION_MEMBERS: [&str; 8] = ["kind", "subject", "scheme", "scheme_digest", "payload", "issued_at", "signer_pubkey", "sig"]; +/// The scheme the conformance corpus names (§7.4): a slot, with no algorithm. +pub const CONFORMANCE_SCHEME: &str = "wai-mark:conformance"; + +#[derive(Clone, Debug)] +pub struct Marking { + pub link: [u8; 32], + pub subject: [u8; 32], + pub origin: [u8; 32], + pub scheme: String, + pub scheme_digest: [u8; 32], + pub payload: [u8; 8], +} + +#[derive(Clone, Debug)] +pub struct Detection { + pub link: [u8; 32], + pub subject: [u8; 32], + pub scheme: String, + pub scheme_digest: [u8; 32], + pub payload: Option<[u8; 8]>, +} + +pub fn read_marking(text: &str) -> Result { + let (v, signer, link) = read_record(text, "wai-marking", &MARKING_MEMBERS)?; + let f = || { + Some(Marking { + link, + subject: hexn(&v["subject"])?, + origin: hexn(&v["origin"])?, + scheme: term(&v["scheme"])?, + scheme_digest: hexn(&v["scheme_digest"])?, + payload: hexn(&v["payload"])?, + }) + }; + let m = f().ok_or(Reason::RecordMalformed)?; + check_signature(&v, &signer)?; + Ok(m) +} + +pub fn read_detection(text: &str) -> Result { + let (v, signer, link) = read_record(text, "wai-detection", &DETECTION_MEMBERS)?; + let f = || { + Some(Detection { + link, + subject: hexn(&v["subject"])?, + scheme: term(&v["scheme"])?, + scheme_digest: hexn(&v["scheme_digest"])?, + payload: nullable(&v["payload"], hexn)?, + }) + }; + let d = f().ok_or(Reason::RecordMalformed)?; + check_signature(&v, &signer)?; + Ok(d) +} + +// --------------------------------------------------------------------------------------- +// §9 Reviews +// --------------------------------------------------------------------------------------- + +pub const REVIEW_MEMBERS: [&str; 14] = + ["kind", "subject", "origin", "tier", "reconstruction", "decoder", "variant", "scope", "verdict", "notes", "role", "issued_at", "signer_pubkey", "sig"]; +pub const REVIEW_TIERS: [&str; 2] = ["decode-equivalence", "generation-equivalence"]; +pub const PATH_TIERS: [&str; 4] = ["decode-equivalence", "generation-equivalence", "entropy-consistency", "none"]; + +#[derive(Clone, Debug)] +pub struct Review { + pub link: [u8; 32], + pub signer: [u8; 32], + pub subject: [u8; 32], + pub origin: [u8; 32], + pub tier: String, + pub reconstruction: [u8; 32], + pub scope: String, + pub verdict: String, +} + +pub fn read_review(text: &str) -> Result { + let (v, signer, link) = read_record(text, "wai-review", &REVIEW_MEMBERS)?; + let f = || { + let verdict = one_of(&v["verdict"], &["approved", "approved-with-notes", "rejected"])?; + let notes: Option<[u8; 32]> = nullable(&v["notes"], hexn)?; + if notes.is_none() != (verdict == "approved") { + return None; + } + nullable(&v["variant"], term)?; + nullable(&v["role"], term)?; + term(&v["decoder"])?; + Some(Review { + link, + signer, + subject: hexn(&v["subject"])?, + origin: hexn(&v["origin"])?, + tier: one_of(&v["tier"], &REVIEW_TIERS)?, + reconstruction: hexn(&v["reconstruction"])?, + scope: one_of(&v["scope"], &["accuracy", "rendering", "translation"])?, + verdict, + }) + }; + let r = f().ok_or(Reason::RecordMalformed)?; + check_signature(&v, &signer)?; + Ok(r) +} + +// --------------------------------------------------------------------------------------- +// §10 Label display +// --------------------------------------------------------------------------------------- + +pub const POLICY_MEMBERS: [&str; 9] = ["kind", "classes", "visual", "audio", "interval_us", "grace_us", "issued_at", "signer_pubkey", "sig"]; +pub const PLAYER_MEMBERS: [&str; 8] = ["kind", "session", "seq", "prev", "at", "entry", "signer_pubkey", "sig"]; + +/// The EU icons of the Code of Practice's Annex 1, pinned by SHA-256 as published (§10.2). +pub const EU_ICONS: [(&str, &str); 24] = [ + ("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"), +]; + +/// The pinned icon a digest is, if it is one (§10.2). +pub fn eu_icon(digest: &[u8; 32]) -> Option<&'static str> { + let h = hex(digest); + EU_ICONS.iter().find(|(_, d)| *d == h).map(|(n, _)| *n) +} + +#[derive(Clone, Debug)] +pub struct LabelPolicy { + pub classes: Vec, + pub visual: [u8; 32], + pub audio: Option<[u8; 32]>, + pub interval_us: Option, + pub grace_us: u64, +} + +pub fn read_label_policy(text: &str) -> Result { + let (v, signer, _) = read_record(text, "wai-label-policy", &POLICY_MEMBERS)?; + let f = || { + let classes: Vec = v["classes"].as_array()?.iter().map(|c| Class::from_name(c.as_str()?)).collect::>()?; + if classes.is_empty() || !classes.windows(2).all(|w| w[0] < w[1]) { + return None; + } + let interval_us = nullable(&v["interval_us"], int)?; + if interval_us == Some(0) { + return None; + } + Some(LabelPolicy { classes, visual: hexn(&v["visual"])?, audio: nullable(&v["audio"], hexn)?, interval_us, grace_us: int(&v["grace_us"])? }) + }; + let p = f().ok_or(Reason::RecordMalformed)?; + check_signature(&v, &signer)?; + Ok(p) +} + +/// One entry of a player's log (§10.3). +#[derive(Clone, Debug)] +pub enum Entry { + Present { subject: [u8; 32], origin: Option<[u8; 32]>, class: Option, media: String, tier: String, output: Option<[u8; 32]> }, + Shown { audio: bool, asset: [u8; 32], trigger: String }, + Resume, + Review { subject: [u8; 32], review: [u8; 32] }, +} + +#[derive(Clone, Debug)] +pub struct PlayerEntry { + pub link: [u8; 32], + pub signer: [u8; 32], + pub session: [u8; 32], + pub seq: u64, + pub prev: Option<[u8; 32]>, + pub at: u64, + pub entry: Entry, +} + +fn read_entry(v: &Value) -> Option { + match v.get("type")?.as_str()? { + "present" => { + let o = exact(v, &["type", "subject", "origin", "class", "media", "tier", "decoder", "variant", "output"])?; + term(&o["decoder"])?; + nullable(&o["variant"], term)?; + Some(Entry::Present { + subject: hexn(&o["subject"])?, + origin: nullable(&o["origin"], hexn)?, + class: nullable(&o["class"], |c| Class::from_name(c.as_str()?))?, + media: one_of(&o["media"], &MEDIA)?, + tier: one_of(&o["tier"], &PATH_TIERS)?, + output: nullable(&o["output"], hexn)?, + }) + } + "shown" => { + let o = exact(v, &["type", "label", "asset", "trigger"])?; + let audio = match o["label"].as_str()? { + "visual" => false, + "audio" => true, + _ => return None, + }; + Some(Entry::Shown { audio, asset: hexn(&o["asset"])?, trigger: one_of(&o["trigger"], &["first-exposure", "resume", "interval"])? }) + } + "resume" => { + let o = exact(v, &["type", "after"])?; + one_of(&o["after"], &["ad-break", "other"])?; + Some(Entry::Resume) + } + "review" => { + let o = exact(v, &["type", "subject", "review"])?; + Some(Entry::Review { subject: hexn(&o["subject"])?, review: hexn(&o["review"])? }) + } + _ => None, + } +} + +pub fn read_player_entry(text: &str) -> Result { + let (v, signer, link) = read_record(text, "wai-player", &PLAYER_MEMBERS)?; + let f = || { + Some(PlayerEntry { + link, + signer, + session: hexn(&v["session"])?, + seq: int(&v["seq"])?, + prev: nullable(&v["prev"], hexn)?, + at: int(&v["at"])?, + entry: read_entry(&v["entry"])?, + }) + }; + let e = f().ok_or(Reason::RecordMalformed)?; + check_signature(&v, &signer)?; + Ok(e) +} + +// --------------------------------------------------------------------------------------- +// §11 Verification +// --------------------------------------------------------------------------------------- + +/// A room a case holds (§11.1): live-rooms §9.1's inputs. +#[derive(Clone, Debug, Default)] +pub struct RoomInput { + pub descriptor: Vec, + pub grants: Vec>, + pub head: Option, + pub chains: Vec>>, +} + +/// A verifier's policy (§11.1). +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct Policy { + /// `dual_layer` `required`: every synthesized object's claim has a marking. + pub dual_layer_required: bool, + /// `assistive` `require-proof`: every assistive claim's evidence is recomputed. + pub require_proof: bool, +} + +/// What a verifier is given (§11.1). +#[derive(Clone, Debug, Default)] +pub struct Case { + pub claims: Vec, + pub markings: Vec, + pub detections: Vec, + pub reviews: Vec, + pub label_policy: Option, + pub player: Vec, + /// Envelopes the verifier holds; each is named by its content hash. + pub objects: Vec>, + /// Canonical buffers a sink decoded, by the content hash of the object decoded. + pub decoded: Vec<([u8; 32], Vec)>, + pub room: Option, + pub policy: Policy, +} + +/// Where a case is refused (§11.2). +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum At { + Claim(usize), + Marking(usize), + Detection(usize), + Review(usize), + LabelPolicy, + Player(usize), + Room(room::At), +} + +impl At { + pub fn to_json(&self) -> Value { + match self { + At::Claim(i) => json!(["claim", i]), + At::Marking(i) => json!(["marking", i]), + At::Detection(i) => json!(["detection", i]), + At::Review(i) => json!(["review", i]), + At::LabelPolicy => json!(["label_policy"]), + At::Player(i) => json!(["player", i]), + At::Room(a) => match a { + room::At::Descriptor => json!(["room", "descriptor"]), + room::At::KeyLog => json!(["room", "key_log"]), + room::At::Grant(i) => json!(["room", "grant", i]), + room::At::Record { chain, record } => json!(["room", "record", chain, record]), + }, + } + } +} + +/// What an accepted case states (§11.3). +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Report { + pub claims: Vec, + pub detections: Vec, + pub ai_tracks: Vec, + pub reviews_presented: Vec, + pub labels: Vec, + pub visual_icon: Option, + pub room: Option<&'static str>, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Outcome { + Accept(Report), + Refuse { at: At, reason: Reason }, +} + +type Step6 = Result; + +/// The manifest of a `WAI1` envelope (SPEC §2), or `None` for bytes that are not one. +fn envelope_manifest(b: &[u8]) -> Option { + if b.len() < 8 || &b[..4] != b"WAI1" { + return None; + } + let n = u32::from_le_bytes(b[4..8].try_into().ok()?) as usize; + serde_json::from_slice(b.get(8..8 + n)?).ok() +} + +/// §3.3: whether an envelope's capability generates: its registry class is +/// `GenerativeDeterministic` or `Behavioral`, or its `intent` is `create`. +pub fn generates(envelope: &[u8]) -> bool { + use crate::codecs::ReplicateClass as R; + let Some(m) = envelope_manifest(envelope) else { return false }; + let cap = m["model_requirement"]["capability"].as_str().unwrap_or(""); + matches!(crate::codecs::capability_replicate_class(cap), Some(R::GenerativeDeterministic | R::Behavioral)) || m["intent"] == "create" +} + +/// The digest of a room track (live-rooms §6.2): SHA-256 over the rendering of its +/// namespace, one zero byte, and its name. +pub fn track_digest(room_id: &[u8; 32], owner: &str, media: &str, layer: u8) -> [u8; 32] { + let ns = vec![hex(room_id), owner.to_string(), media.to_string()]; + sha256(&[room::render_namespace(&ns).as_bytes(), &[0], layer.to_string().as_bytes()]) +} + +/// Verify a case (§11.2). +pub fn verify(case: &Case) -> Outcome { + match run(case) { + Ok(r) => Outcome::Accept(r), + Err((at, reason)) => Outcome::Refuse { at, reason }, + } +} + +fn run(case: &Case) -> Step6 { + let mut report = Report::default(); + // Step 1: every record reads and its signature verifies. + let claims: Vec = case.claims.iter().enumerate().map(|(i, t)| read_origin(t).map_err(|r| (At::Claim(i), r))).collect::>()?; + let markings: Vec = case.markings.iter().enumerate().map(|(i, t)| read_marking(t).map_err(|r| (At::Marking(i), r))).collect::>()?; + let detections: Vec = + case.detections.iter().enumerate().map(|(i, t)| read_detection(t).map_err(|r| (At::Detection(i), r))).collect::>()?; + let reviews: Vec = case.reviews.iter().enumerate().map(|(i, t)| read_review(t).map_err(|r| (At::Review(i), r))).collect::>()?; + let label_policy = match &case.label_policy { + Some(t) => Some(read_label_policy(t).map_err(|r| (At::LabelPolicy, r))?), + None => None, + }; + let player: Vec = case.player.iter().enumerate().map(|(i, t)| read_player_entry(t).map_err(|r| (At::Player(i), r))).collect::>()?; + + let by_link: BTreeMap<[u8; 32], &Origin> = claims.iter().map(|c| (c.link, c)).collect(); + let objects: BTreeMap<[u8; 32], &Vec> = case.objects.iter().map(|o| (crate::jwp_hash::content_hash(o), o)).collect(); + let decoded: BTreeMap<[u8; 32], &Vec> = case.decoded.iter().map(|(h, b)| (*h, b)).collect(); + + // Step 2: each origin claim. + let mut subjects = BTreeSet::new(); + for (i, c) in claims.iter().enumerate() { + let at = At::Claim(i); + let e = |r| (at.clone(), r); + if !subjects.insert(jcs(&c.value["subject"]).expect("a subject in the subset")) { + return Err(e(Reason::ClaimConflict)); + } + if c.class != c.composed_class() { + return Err(e(Reason::ClassMismatch)); + } + if c.source != c.computed_source() { + return Err(e(Reason::SourceMismatch)); + } + for input in &c.inputs { + if let Some(held) = input.origin.and_then(|h| by_link.get(&h)) + && (held.subject != Subject::Object(input.content_hash) || held.class != input.class || held.source != input.source) + { + return Err(e(Reason::InputMismatch)); + } + } + if let Subject::Object(h) = c.subject + && objects.get(&h).is_some_and(|env| generates(env)) + && c.step != Step::Generate + { + return Err(e(Reason::ClassUnderstated)); + } + let mut status = Value::Null; + if let Some(ev) = &c.evidence { + if ev.replicate && !replicate_evidence_ok(ev) { + return Err(e(Reason::EvidenceInvalid)); + } + status = json!(if ev.replicate { "not-recomputed" } else { "asserted" }); + if ev.replicate + && let Some(input) = decoded.get(&c.inputs[0].content_hash) + { + let (w, h) = (ev.width as usize, ev.height as usize); + if input.len() != w * h * 3 || sha256(&[input]) != ev.input_samples { + return Err(e(Reason::InputSamplesMismatch)); + } + let out = derive(&ev.derivation, input, w, h).expect("a pinned derivation"); + if sha256(&[&out]) != ev.output_samples { + return Err(e(Reason::AssistiveRecomputeMismatch)); + } + status = json!("proved"); + } + if let Subject::Object(h) = c.subject + && decoded.get(&h).is_some_and(|b| sha256(&[b]) != ev.output_samples) + { + return Err(e(Reason::SamplesMismatch)); + } + if case.policy.require_proof && status != "proved" { + return Err(e(Reason::AssistiveNotProved)); + } + } + report.claims.push(json!({ + "link": hex(&c.link), + "class": c.class.name(), + "source": c.source, + "c2pa": c.export(), + "assistive": status, + })); + } + + // Step 3: markings, detections, and the two layers. + for (i, m) in markings.iter().enumerate() { + let c = by_link.get(&m.origin).ok_or((At::Marking(i), Reason::ClaimUnknown))?; + if c.subject != Subject::Object(m.subject) || c.mark_payload() != m.payload { + return Err((At::Marking(i), Reason::MarkingMismatch)); + } + } + for (i, d) in detections.iter().enumerate() { + let e = |r| (At::Detection(i), r); + let claim = claims.iter().find(|c| c.subject == Subject::Object(d.subject)); + let marking = markings.iter().find(|m| m.subject == d.subject); + if let Some(m) = marking + && (m.scheme != d.scheme || m.scheme_digest != d.scheme_digest) + { + return Err(e(Reason::DetectionMismatch)); + } + let result = match (d.payload, claim) { + (None, _) if marking.is_some() => return Err(e(Reason::WatermarkAbsent)), + (None, _) => "unmarked", + (Some(_), None) => "not-checked", + (Some(p), Some(c)) if p != c.mark_payload() => return Err(e(Reason::LayersDisagree)), + (Some(_), Some(_)) => "agree", + }; + report.detections.push(json!({"subject": hex(&d.subject), "result": result})); + } + if case.policy.dual_layer_required { + for (i, c) in claims.iter().enumerate() { + if c.class == Class::Synthesized && matches!(c.subject, Subject::Object(_)) && !markings.iter().any(|m| m.origin == c.link) { + return Err((At::Claim(i), Reason::LayerMissing)); + } + } + } + + // Step 4: reviews. + for (i, r) in reviews.iter().enumerate() { + let c = by_link.get(&r.origin).ok_or((At::Review(i), Reason::ClaimUnknown))?; + if c.subject != Subject::Object(r.subject) || c.class != Class::Synthesized { + return Err((At::Review(i), Reason::ReviewSubjectMismatch)); + } + } + + // Step 5: the room, and the tracks of its AI participants. + if let Some(ri) = &case.room { + let run = room::verify_room(&ri.descriptor, &ri.grants, ri.head.as_deref(), &ri.chains); + report.room = Some(match &run.outcome { + room::RoomOutcome::Refuse { at, reason } => return Err((At::Room(*at), Reason::Room(*reason))), + room::RoomOutcome::Gap(_) => "gap", + room::RoomOutcome::Accept => "accept", + }); + let room_id = run.room.expect("a room that read"); + let d = run.descriptor.as_ref().expect("a descriptor that read"); + let grants: BTreeMap<[u8; 32], &room::Grant> = run.grants.iter().map(|h| (h.digest, &h.grant)).collect(); + let mut current: BTreeMap<[u8; 32], [u8; 32]> = BTreeMap::new(); + for &(chain, record, under) in &run.order { + if under.is_some() { + continue; + } + let ev = Event::parse(&ri.chains[chain][record]).expect("a record the room accepted"); + let Ok(Payload::Lifecycle { kind, subject, detail }) = crate::live_events::parse_payload(ev.kind, &ev.payload, u16::MAX) else { continue }; + let Ok(detail) = Detail::parse(kind, &detail) else { continue }; + let (slot, m, layers) = match detail { + Detail::Join { grant } => { + current.insert(subject, grant); + continue; + } + Detail::GrantExchange { new, .. } => { + current.insert(subject, new); + continue; + } + Detail::Leave { .. } => { + current.remove(&subject); + continue; + } + Detail::Publish { slot, media, layers, .. } | Detail::Takeover { slot, media, layers, .. } => (slot, media, layers), + _ => continue, + }; + let Some(model) = current.get(&subject).and_then(|g| grants.get(g)).and_then(|g| g.ai_model()) else { continue }; + let owner = if slot.is_empty() { hex(&subject) } else { format!("_{slot}") }; + let media = room::media_name(m).expect("a media kind the room accepted"); + for layer in 0..layers { + let track = track_digest(&room_id, &owner, media, layer); + let found = claims.iter().find(|c| { + matches!(c.subject, Subject::Track { track: t, .. } if t == track) + && c.class == Class::Synthesized + && c.step == Step::Generate + && c.models.iter().any(|x| x.digest == model) + && (c.signer == subject || d.is_room_key(&c.signer)) + }); + match found { + Some(c) => report.ai_tracks.push(json!({"track": hex(&track), "claim": hex(&c.link)})), + None => return Err((At::Room(room::At::Record { chain, record }), Reason::AiTrackUnlabelled)), + } + } + } + } + + // Step 6: the player's log. + for (j, p) in player.iter().enumerate() { + let first = &player[0]; + let ok = p.seq == j as u64 + && p.session == first.session + && p.signer == first.signer + && if j == 0 { p.prev.is_none() } else { p.prev == Some(player[j - 1].link) && p.at >= player[j - 1].at }; + if !ok { + return Err((At::Player(j), Reason::PlayerChainBroken)); + } + } + for (j, p) in player.iter().enumerate() { + let e = |r| (At::Player(j), r); + match &p.entry { + Entry::Present { subject, origin, class, output, .. } => { + if let Some(c) = origin.and_then(|h| by_link.get(&h)) + && (c.subject != Subject::Object(*subject) || *class != Some(c.class)) + { + return Err(e(Reason::PresentMismatch)); + } + if let (Some(o), Some(b)) = (output, decoded.get(subject)) + && sha256(&[b]) != *o + { + return Err(e(Reason::PresentOutputIrreproducible)); + } + } + Entry::Review { subject, review } => { + let r = reviews.iter().find(|r| r.link == *review).ok_or(e(Reason::ClaimUnknown))?; + let shown = player[..j].iter().rev().find_map(|q| match &q.entry { + Entry::Present { subject: s, tier, output, .. } if s == subject => Some((tier, output)), + _ => None, + }); + let Some((tier, output)) = shown.filter(|_| r.subject == *subject) else { + return Err(e(Reason::ReviewSubjectMismatch)); + }; + if r.verdict == "rejected" { + return Err(e(Reason::ReviewRejected)); + } + if *tier != r.tier { + return Err(e(Reason::ReviewPathNotExact)); + } + if *output != Some(r.reconstruction) { + return Err(e(Reason::ReviewReconstructionMismatch)); + } + report.reviews_presented.push(json!({"entry": j, "review": hex(review)})); + } + _ => {} + } + } + if let Some(lp) = &label_policy { + report.visual_icon = eu_icon(&lp.visual).map(String::from); + labels(&player, lp, &mut report)?; + } + Ok(report) +} + +/// §10.4: the label obligations of a player's log under a label policy. +fn labels(player: &[PlayerEntry], lp: &LabelPolicy, report: &mut Report) -> Step6<()> { + for (j, p) in player.iter().enumerate() { + if let Entry::Shown { audio, asset, .. } = &p.entry { + let want = if *audio { lp.audio } else { Some(lp.visual) }; + if want != Some(*asset) { + return Err((At::Player(j), Reason::LabelAssetMismatch)); + } + } + } + let shown_at = |audio: bool, lo: u64, hi: u64| { + player.iter().position(|q| matches!(q.entry, Entry::Shown { audio: a, .. } if a == audio) && lo <= q.at && q.at <= hi) + }; + // The index of the last labelled presentation of each kind (visual, audio). + let mut last: [Option; 2] = [None, None]; + for (j, p) in player.iter().enumerate() { + let Entry::Present { class: Some(class), media, .. } = &p.entry else { continue }; + if !lp.classes.contains(class) { + continue; + } + let audio = media == "audio"; + let hi = p.at.saturating_add(lp.grace_us); + let resumed = |from: usize| player[from..j].iter().rposition(|q| matches!(q.entry, Entry::Resume)).map(|k| player[from + k].at); + let (trigger, found) = match last[audio as usize] { + None => ("first-exposure", shown_at(audio, 0, hi)), + Some(prev) => match resumed(prev + 1) { + Some(r) => ("resume", shown_at(audio, r, hi)), + None => match lp.interval_us { + Some(iv) => ("interval", shown_at(audio, p.at.saturating_sub(iv), hi)), + None => { + last[audio as usize] = Some(j); + continue; + } + }, + }, + }; + let Some(k) = found else { return Err((At::Player(j), Reason::LabelNotShown)) }; + report.labels.push(json!({"entry": j, "trigger": trigger, "shown": k})); + last[audio as usize] = Some(j); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_derivations_are_the_rules_of_section_4() { + // A 2 × 1 image: one black and one white pixel. + let input = [0, 0, 0, 255, 255, 255]; + let up = upscale2(&input, 2, 1); + assert_eq!(up.len(), 4 * 2 * 3); + // Row 0: a, (a+b+1)>>1, b, b (the last column repeats). + assert_eq!(&up[..12], &[0, 0, 0, 128, 128, 128, 255, 255, 255, 255, 255, 255]); + // Row 1 repeats row 0, the last row being its own neighbour. + assert_eq!(&up[12..], &up[..12]); + let flat = vec![77u8; 5 * 4 * 3]; + assert_eq!(denoise3(&flat, 5, 4), flat); + let mut dot = vec![0u8; 3 * 3 * 3]; + dot[(3 + 1) * 3] = 160; + let d = denoise3(&dot, 3, 3); + assert_eq!(d[(3 + 1) * 3] as u32, (160 * 4 + 8) >> 4); + assert_eq!(d[0] as u32, (160 + 8) >> 4); + } + + #[test] + fn the_class_rises_along_a_chain() { + assert!(Class::Captured < Class::Reconstructed && Class::Reconstructed < Class::Assistive && Class::Assistive < Class::Synthesized); + assert_eq!(Step::Convert.class(), Class::Reconstructed); + } + + #[test] + fn a_generator_envelope_generates() { + let env = |m: &str| { + let mut b = b"WAI1".to_vec(); + b.extend_from_slice(&(m.len() as u32).to_le_bytes()); + b.extend_from_slice(m.as_bytes()); + b.extend_from_slice(&0u32.to_le_bytes()); + b + }; + assert!(generates(&env(r#"{"wai":"1.0","media":"audio","intent":"create","model_requirement":{"capability":"wai.generate.spectral"}}"#))); + assert!(generates(&env(r#"{"wai":"1.0","media":"image","intent":"perceptual","model_requirement":{"capability":"wai.semantic.clip"}}"#))); + assert!(!generates(&env(r#"{"wai":"1.0","media":"image","intent":"replicate","model_requirement":{"capability":"wai.image.png"}}"#))); + assert!(!generates(b"not an envelope")); + } +} diff --git a/wai/wai-rs/src/synthesis/corpus.rs b/wai/wai-rs/src/synthesis/corpus.rs new file mode 100644 index 000000000..6be2910d2 --- /dev/null +++ b/wai/wai-rs/src/synthesis/corpus.rs @@ -0,0 +1,1092 @@ +//! The conformance corpus of extensions/synthesis.md §13: `live-conformance/synthesis/`. +//! +//! [`generate`] writes it from fixed seeds: every key is SHA-256 of this corpus's label and +//! an index, every sample buffer is SHA-256 output of a label, every time and text is written +//! here, and Ed25519 signing is deterministic (RFC 8032), so the file is the same bytes on +//! every machine. Each case states the outcome it was written to have; the generator asserts +//! that this module reaches it before writing it, and that the corpus gives every reason of +//! §12. +//! +//! [`verify`] checks a corpus directory's files against this module. +//! `live-conformance/synthesis_verify.py` checks the same file from the extension's text, +//! sharing no code with this module. + +use super::*; +use crate::live_events::{kind, Caption, Event, Origin as Said, Policy as EventPolicy}; +use crate::live_room::{media, Caps, Descriptor, Grant, Limits, Recording, Review as RoomReview, Role, NO_CEILING}; +use serde_json::{json, Map, Value}; + +const T0_US: u64 = 1_792_108_800_000_000; +const T0_S: u64 = T0_US / 1_000_000; +const S: u64 = 1_000_000; +const ISSUED: &str = "2026-10-21T09:00:00Z"; + +const CAMERA: u8 = 0; +const CODEC: u8 = 1; +const EDITOR: u8 = 2; +const GENERATOR: u8 = 3; +const MARKER: u8 = 4; +const DETECTOR: u8 = 5; +const REVIEWER: u8 = 6; +const DEPLOYER: u8 = 7; +const PLAYER: u8 = 8; +const AUTHORITY: u8 = 9; +const CLOCK: u8 = 10; +const AI_HOST: u8 = 11; +const VIEWER: u8 = 12; + +fn sha(parts: &[&[u8]]) -> [u8; 32] { + sha256(parts) +} + +fn key(i: u8) -> SigningKey { + SigningKey::from_bytes(&sha(&[b"wai:synthesis/corpus/key\x00", &[i]])) +} + +fn pk(i: u8) -> [u8; 32] { + key(i).verifying_key().to_bytes() +} + +fn label(s: &str) -> [u8; 32] { + sha(&[b"wai:synthesis/corpus/label\x00", s.as_bytes()]) +} + +/// `n` bytes from a label: SHA-256 of the label and a counter, concatenated. +fn bytes(s: &str, n: usize) -> Vec { + (0u32..).flat_map(|i| sha(&[s.as_bytes(), &i.to_le_bytes()])).take(n).collect() +} + +fn text(v: &Value) -> String { + jcs(v).expect("a record in the subset") +} + +/// A `WAI1` envelope (SPEC §2) of this manifest and payload. +fn envelope(manifest: &str, payload: &[u8]) -> Vec { + let mut b = b"WAI1".to_vec(); + b.extend_from_slice(&(manifest.len() as u32).to_le_bytes()); + b.extend_from_slice(manifest.as_bytes()); + b.extend_from_slice(&(payload.len() as u32).to_le_bytes()); + b.extend_from_slice(payload); + b +} + +fn manifest(media: &str, intent: &str, cap: &str) -> String { + format!(r#"{{"wai":"1.0","media":"{media}","intent":"{intent}","model_requirement":{{"capability":"{cap}"}}}}"#) +} + +// ---- the objects and their samples ---- + +const W: usize = 4; +const H: usize = 3; + +struct Objects { + photo: Vec, + coded: Vec, + upscaled: Vec, + denoised: Vec, + sr: Vec, + video: Vec, + spectral: Vec, + inpainted: Vec, + composite: Vec, + transcode: Vec, + /// The canonical buffers: the photo's, the codec's reconstruction of it, and its two + /// pinned derivations; the generated video's first frame. + photo_px: Vec, + coded_px: Vec, + up_px: Vec, + dn_px: Vec, + video_px: Vec, +} + +fn objects() -> Objects { + let photo_px = bytes("photo", W * H * 3); + // The codec's reconstruction: the photo quantised to multiples of 3, so that both the + // derivations' roundings decide some of their samples. + let coded_px: Vec = photo_px.iter().map(|b| b - b % 3).collect(); + let up_px = upscale2(&coded_px, W, H); + let dn_px = denoise3(&coded_px, W, H); + let png = |s: &str| envelope(&manifest("image", "replicate", "wai.image.png"), &bytes(s, 48)); + Objects { + photo: png("photo envelope"), + coded: envelope(&manifest("image", "replicate", "wai.neural.int_hyper"), &bytes("coded", 40)), + upscaled: png("upscaled envelope"), + denoised: png("denoised envelope"), + sr: png("float super-resolution envelope"), + video: envelope(&manifest("video", "perceptual", "wai.video.av1"), &bytes("generated video", 64)), + spectral: envelope(&manifest("audio", "create", "wai.generate.spectral"), br#"{"form":0}"#), + inpainted: png("inpainted envelope"), + composite: png("composite envelope"), + transcode: envelope(&manifest("video", "perceptual", "wai.video.hevc"), &bytes("transcoded video", 64)), + photo_px, + coded_px, + up_px, + dn_px, + video_px: bytes("generated video frame", W * H * 3), + } +} + +fn ch(env: &[u8]) -> [u8; 32] { + crate::jwp_hash::content_hash(env) +} + +// ---- records ---- + +fn object_subject(env: &[u8]) -> Value { + json!({"type": "object", "content_hash": hex(&ch(env))}) +} + +#[allow(clippy::too_many_arguments)] +fn claim_body(subject: Value, media: &str, class: &str, step: &str, operation: &str, source: &str, models: Value, oversight: Value, inputs: Value, evidence: Value) -> Value { + json!({ + "kind": "wai-origin", + "subject": subject, + "media": media, + "class": class, + "step": step, + "operation": operation, + "record": null, + "source": source, + "models": models, + "oversight": oversight, + "inputs": inputs, + "evidence": evidence, + "issued_at": ISSUED, + }) +} + +/// The input entry naming a held claim (§3.1). +fn input_of(c: &Value) -> Value { + json!({ + "content_hash": c["subject"]["content_hash"], + "origin": hex(&link_hash(c)), + "class": c["class"], + "source": c["source"], + }) +} + +fn trained(s: &str) -> Value { + json!({"digest": hex(&label(s)), "type": "trained"}) +} + +fn evidence(tier: &str, derivation: &str, input: &[u8], output: &[u8]) -> Value { + json!({ + "tier": tier, + "derivation": derivation, + "params": {}, + "width": W, + "height": H, + "input_samples": hex(&sha(&[input])), + "output_samples": hex(&sha(&[output])), + }) +} + +/// The claims of the corpus's chain and of its generated objects, each a body before signing. +struct Bodies { + photo: Value, + coded: Value, + up: Value, + dn: Value, + sr: Value, + video: Value, + spectral: Value, +} + +fn signed(body: Value, who: u8) -> Value { + sign(body, &key(who)) +} + +fn bodies(o: &Objects) -> Bodies { + let photo = claim_body(object_subject(&o.photo), "image", "captured", "capture", "capture", "digitalCapture", json!([]), Value::Null, json!([]), Value::Null); + let photo_s = signed(photo.clone(), CAMERA); + let coded = + claim_body(object_subject(&o.coded), "image", "reconstructed", "convert", "wai.neural.int_hyper", "digitalCapture", json!([]), Value::Null, json!([input_of(&photo_s)]), Value::Null); + let coded_s = signed(coded.clone(), CODEC); + let up = claim_body( + object_subject(&o.upscaled), + "image", + "assistive", + "assist", + UPSCALE2, + "algorithmicallyEnhanced", + json!([]), + Value::Null, + json!([input_of(&coded_s)]), + evidence("replicate", UPSCALE2, &o.coded_px, &o.up_px), + ); + let dn = claim_body( + object_subject(&o.denoised), + "image", + "assistive", + "assist", + DENOISE3, + "algorithmicallyEnhanced", + json!([]), + Value::Null, + json!([input_of(&coded_s)]), + evidence("replicate", DENOISE3, &o.coded_px, &o.dn_px), + ); + let sr = claim_body( + object_subject(&o.sr), + "image", + "assistive", + "assist", + "super-resolution", + "algorithmicallyEnhanced", + json!([trained("float super-resolution model")]), + Value::Null, + json!([input_of(&coded_s)]), + json!({ + "tier": "attest", + "derivation": "super-resolution", + "params": {"scale": 2}, + "width": W, + "height": H, + "input_samples": hex(&sha(&[&o.coded_px])), + "output_samples": hex(&label("float super-resolution output")), + }), + ); + let video = claim_body( + object_subject(&o.video), + "video", + "synthesized", + "generate", + "video-generation", + "trainedAlgorithmicMedia", + json!([trained("video model")]), + json!("fully_autonomous"), + json!([]), + Value::Null, + ); + let spectral = claim_body( + object_subject(&o.spectral), + "audio", + "synthesized", + "generate", + "wai.generate.spectral", + "algorithmicMedia", + json!([{"digest": hex(&label("spectral model bundle")), "type": "algorithmic"}]), + json!("prompt_guided"), + json!([]), + Value::Null, + ); + Bodies { photo, coded, up, dn, sr, video, spectral } +} + +const SCHEME_DOC: &[u8] = b"wai-mark:conformance names no watermark algorithm (synthesis.md section 7.4)"; + +fn marking(c: &Value, payload: Option<[u8; 8]>) -> Value { + let p = payload.unwrap_or_else(|| read_origin(&text(c)).expect("a corpus claim").mark_payload()); + signed( + json!({ + "kind": "wai-marking", + "subject": c["subject"]["content_hash"], + "origin": hex(&link_hash(c)), + "scheme": CONFORMANCE_SCHEME, + "scheme_digest": hex(&sha(&[SCHEME_DOC])), + "payload": hex(&p), + "issued_at": ISSUED, + }), + MARKER, + ) +} + +fn detection(subject: &Value, payload: Option<[u8; 8]>, scheme_doc: &[u8]) -> Value { + signed( + json!({ + "kind": "wai-detection", + "subject": subject, + "scheme": CONFORMANCE_SCHEME, + "scheme_digest": hex(&sha(&[scheme_doc])), + "payload": payload.map(|p| hex(&p)), + "issued_at": ISSUED, + }), + DETECTOR, + ) +} + +fn payload_of(c: &Value) -> [u8; 8] { + read_origin(&text(c)).expect("a corpus claim").mark_payload() +} + +fn review(c: &Value, reconstruction: &[u8; 32], tier: &str, verdict: &str) -> Value { + signed( + json!({ + "kind": "wai-review", + "subject": c["subject"]["content_hash"], + "origin": hex(&link_hash(c)), + "tier": tier, + "reconstruction": hex(reconstruction), + "decoder": "builtin:wai@0.3.0", + "variant": null, + "scope": "rendering", + "verdict": verdict, + "notes": if verdict == "approved" { Value::Null } else { json!(hex(&label("review notes"))) }, + "role": null, + "issued_at": ISSUED, + }), + REVIEWER, + ) +} + +fn icon(name: &str) -> [u8; 32] { + let h = EU_ICONS.iter().find(|(n, _)| *n == name).expect("a pinned icon").1; + unhex(h).expect("hex").try_into().expect("32 bytes") +} + +fn label_policy(interval_us: Option, grace_us: u64) -> Value { + signed( + json!({ + "kind": "wai-label-policy", + "classes": ["synthesized"], + "visual": hex(&icon("LABEL_AI GENERATED_black.svg")), + "audio": hex(&label("audible disclaimer")), + "interval_us": interval_us, + "grace_us": grace_us, + "issued_at": ISSUED, + }), + DEPLOYER, + ) +} + +/// A player's log being written (§10.3). +struct Log { + session: [u8; 32], + entries: Vec, +} + +impl Log { + fn new() -> Log { + Log { session: label("player session"), entries: vec![] } + } + + fn push(&mut self, at: u64, entry: Value) -> &mut Log { + let prev = self.entries.last().map(|e| json!(hex(&link_hash(e)))).unwrap_or(Value::Null); + let e = signed( + json!({"kind": "wai-player", "session": hex(&self.session), "seq": self.entries.len(), "prev": prev, "at": at, "entry": entry}), + PLAYER, + ); + self.entries.push(e); + self + } + + fn texts(&self) -> Vec { + self.entries.iter().map(text).collect() + } +} + +fn present(c: &Value, tier: &str, output: Option<[u8; 32]>) -> Value { + json!({ + "type": "present", + "subject": c["subject"]["content_hash"], + "origin": hex(&link_hash(c)), + "class": c["class"], + "media": c["media"], + "tier": tier, + "decoder": "builtin:wai@0.3.0", + "variant": null, + "output": output.map(|o| hex(&o)), + }) +} + +fn shown(asset: [u8; 32], trigger: &str) -> Value { + json!({"type": "shown", "label": "visual", "asset": hex(&asset), "trigger": trigger}) +} + +fn resume() -> Value { + json!({"type": "resume", "after": "ad-break"}) +} + +// ---- the room ---- + +const MODEL: &str = "voice agent model"; + +/// One principal's chain being written: its index, its records, its next seq and its head. +type Chain = (u8, Vec>, u64, [u8; 32]); + +/// A room with an AI host whose grant names its model (live-rooms §4.4), a viewer, and the +/// host publishing speech. `said`: the origin its chat states. +fn ai_room(said: Option) -> RoomInput { + let d = Descriptor { + authority: pk(AUTHORITY), + clock: pk(CLOCK), + t0_us: T0_US, + tick_us: S, + origin: String::new(), + limits: Limits { max_present: 8, max_publishers: 2, reval_s: 0, ceiling: EventPolicy::default() }, + roles: vec![ + Role { name: "host".into(), caps: Caps { publish: 1, subscribe: true, max_layer: 0, powers: 0 }, events: bits(&[kind::CHAT, kind::CAPTION]) }, + Role { name: "audience".into(), caps: Caps { publish: 0, subscribe: true, max_layer: 0, powers: 0 }, events: bits(&[kind::CHAT, kind::REACTION]) }, + ], + slots: vec![], + profiles: vec![room::registered_profile("speech").expect("a registered profile")], + review: RoomReview { kinds: [0; 32], deadline_ms: 0, fallback: 0 }, + recording: Recording { mode: 0, retention_days: 0, commit_interval_ms: 0, jitter_ms: 0, regions: vec![] }, + sig: [0; 64], + } + .sign(&key(AUTHORITY)); + let id = d.room_id(); + let grant = |who: u8, role: usize, profiles: Vec, attrs: Vec<(String, String)>| { + let r = &d.roles[role]; + let kinds: Vec = (0..=255u8).filter(|&k| r.events[(k / 8) as usize] >> (k % 8) & 1 == 1).collect(); + Grant { + room: id, + principal: pk(who), + prev: [0; 32], + effective: 0, + nbf: T0_S - 3_600, + exp: T0_S + 86_400, + role: role as u8, + caps: r.caps, + precedence: 0, + once: [0; 16], + viewer: [0; 16], + policy: EventPolicy::with_kinds(&kinds), + joules_ceiling: NO_CEILING, + funds_ceiling: NO_CEILING, + currency: [0; 3], + profiles, + origins: vec!["https://relay.example".into()], + attrs, + sig: [0; 64], + } + .sign(&key(AUTHORITY)) + }; + let host = grant(AI_HOST, 0, vec![0], vec![("ai".into(), hex(&label(MODEL)))]); + let viewer = grant(VIEWER, 1, vec![], vec![]); + let mut chains: Vec = vec![]; + let mut emit = |who: u8, t: u64, p: Payload| { + let i = match chains.iter().position(|c| c.0 == who) { + Some(i) => i, + None => { + chains.push((who, vec![], 0, [0; 32])); + chains.len() - 1 + } + }; + let c = &mut chains[i]; + let e = Event::sign(&key(who), id, c.2, c.3, t, None, p.kind(), p.encode()); + c.2 += 1; + c.3 = e.digest(); + c.1.push(e.to_bytes()); + }; + let life = |d: Detail, subject: u8| Payload::Lifecycle { kind: d.kind(), subject: pk(subject), detail: d.encode() }; + emit(CLOCK, 0, life(Detail::Join { grant: host.digest() }, AI_HOST)); + emit(CLOCK, 0, life(Detail::Join { grant: viewer.digest() }, VIEWER)); + emit(CLOCK, S, life(Detail::Publish { slot: String::new(), media: media::AUDIO, profile: 0, layers: 1 }, AI_HOST)); + emit(VIEWER, 2 * S, Payload::Chat { text: "Hello?".into(), origin: None }); + emit(AI_HOST, 3 * S, Payload::Chat { text: "Hello. I am an automated assistant.".into(), origin: said }); + emit(AI_HOST, 4 * S, Payload::Caption(Caption { duration_ms: 2_000, origin: 2, model: Some(label(MODEL)), lang: "en".into(), text: "Hello.".into() })); + RoomInput { descriptor: d.to_bytes(), grants: vec![host.to_bytes(), viewer.to_bytes()], head: None, chains: chains.into_iter().map(|c| c.1).collect() } +} + +fn bits(ks: &[u8]) -> [u8; 32] { + let mut b = [0u8; 32]; + for &k in ks { + b[(k / 8) as usize] |= 1 << (k % 8); + } + b +} + +fn ai_track_claim(r: &RoomInput) -> Value { + let id = Descriptor::parse(&r.descriptor).expect("the corpus room").room_id(); + let track = track_digest(&id, &hex(&pk(AI_HOST)), "audio", 0); + signed( + claim_body( + json!({"type": "track", "track": hex(&track), "from_group": 0}), + "audio", + "synthesized", + "generate", + "speech-synthesis", + "trainedAlgorithmicMedia", + json!([trained(MODEL)]), + json!("fully_autonomous"), + json!([]), + Value::Null, + ), + AI_HOST, + ) +} + +// ---- vectors ---- + +/// What a vector was written to do. +#[derive(Clone, Debug)] +enum Intent { + Accept, + Refuse(At, Reason), +} + +struct Vector { + name: &'static str, + about: &'static str, + case: Case, + intent: Intent, +} + +fn case(claims: &[&Value]) -> Case { + Case { claims: claims.iter().map(|c| text(c)).collect(), ..Default::default() } +} + +fn decoded(pairs: &[(&[u8], &[u8])]) -> Vec<([u8; 32], Vec)> { + pairs.iter().map(|(env, px)| (ch(env), px.to_vec())).collect() +} + +fn with(mut body: Value, path: &[&str], v: Value) -> Value { + let mut at = &mut body; + for p in &path[..path.len() - 1] { + at = at.get_mut(*p).expect("a member"); + } + at[path[path.len() - 1]] = v; + body +} + +fn vectors() -> Vec { + let o = objects(); + let b = bodies(&o); + let photo = signed(b.photo.clone(), CAMERA); + let coded = signed(b.coded.clone(), CODEC); + let up = signed(b.up.clone(), EDITOR); + let dn = signed(b.dn.clone(), EDITOR); + let sr = signed(b.sr.clone(), EDITOR); + let video = signed(b.video.clone(), GENERATOR); + let spectral = signed(b.spectral.clone(), GENERATOR); + let inpainted = signed( + claim_body( + object_subject(&o.inpainted), + "image", + "synthesized", + "generate", + "inpainting", + "compositeWithTrainedAlgorithmicMedia", + json!([trained("image model")]), + json!("human_validated"), + json!([input_of(&photo)]), + Value::Null, + ), + GENERATOR, + ); + let composite = signed( + claim_body( + object_subject(&o.composite), + "image", + "synthesized", + "convert", + "wai.derive.overlay_composition", + "compositeSynthetic", + json!([]), + Value::Null, + json!([input_of(&photo), input_of(&video)]), + Value::Null, + ), + EDITOR, + ); + let transcode = signed(transcode_body(&o, &video), CODEC); + let chain_decoded = decoded(&[(&o.coded, &o.coded_px), (&o.upscaled, &o.up_px), (&o.denoised, &o.dn_px)]); + let chain = || Case { + claims: [&photo, &coded, &up, &dn].iter().map(|c| text(c)).collect(), + objects: vec![o.photo.clone(), o.coded.clone(), o.upscaled.clone(), o.denoised.clone()], + decoded: chain_decoded.clone(), + policy: Policy { dual_layer_required: false, require_proof: true }, + ..Default::default() + }; + let generated = || Case { + claims: [&photo, &video, &spectral, &inpainted, &composite, &transcode].iter().map(|c| text(c)).collect(), + markings: [marking(&video, None), marking(&spectral, None), marking(&inpainted, None), marking(&composite, None), marking(&transcode, None)] + .iter() + .map(text) + .collect(), + detections: vec![ + text(&detection(&video["subject"]["content_hash"], Some(payload_of(&video)), SCHEME_DOC)), + text(&detection(&photo["subject"]["content_hash"], None, SCHEME_DOC)), + ], + objects: vec![o.photo.clone(), o.video.clone(), o.spectral.clone(), o.inpainted.clone(), o.composite.clone(), o.transcode.clone()], + policy: Policy { dual_layer_required: true, require_proof: false }, + ..Default::default() + }; + let video_out = sha(&[&o.video_px]); + let certified = |log: &Log, rv: &Value, lp: Option<&Value>| Case { + claims: vec![text(&video)], + reviews: vec![text(rv)], + label_policy: lp.map(text), + player: log.texts(), + objects: vec![o.video.clone()], + decoded: decoded(&[(&o.video, &o.video_px)]), + ..Default::default() + }; + let approved = review(&video, &video_out, "decode-equivalence", "approved"); + let gen_icon = icon("LABEL_AI GENERATED_black.svg"); + // The certified session: first exposure labelled, a review presented, an ad break, the + // label again on return, and a presentation within the interval. + let session = |tier: &str, out: Option<[u8; 32]>, rv: &Value| { + let mut l = Log::new(); + l.push(0, shown(gen_icon, "first-exposure")) + .push(0, present(&video, tier, out)) + .push(S, json!({"type": "review", "subject": video["subject"]["content_hash"], "review": hex(&link_hash(rv))})) + .push(20 * S, resume()) + .push(20 * S + 200_000, shown(gen_icon, "resume")) + .push(20 * S + 200_000, present(&video, tier, out)) + .push(40 * S, present(&video, tier, out)); + l + }; + + let mut out = vec![ + Vector { + name: "chain", + about: "a capture, a neural codec's reconstruction of it, and two pinned integer derivations of that reconstruction, each recomputed from its input's decoded samples and proved (§3, §4)", + case: chain(), + intent: Intent::Accept, + }, + Vector { + name: "assistive_asserted", + about: "a float super-resolution step that states the assistive class with attest evidence: asserted, not proved, under a policy that accepts assertions (§4.4)", + case: Case { claims: [&photo, &coded, &sr].iter().map(|c| text(c)).collect(), ..Default::default() }, + intent: Intent::Accept, + }, + Vector { + name: "generated", + about: "a generated video, a pinned algorithmic audio generation, an inpainted capture reviewed by a person, a composite of a capture and the generated video, and a transcode of it; each synthesized object marked in both layers, a detection agreeing with the video's claim, and one finding no mark on the capture (§5, §6, §7)", + case: generated(), + intent: Intent::Accept, + }, + Vector { + name: "ai_room", + about: "a room whose AI host's grant names its model: its chat and caption state that model, and its published speech track has a synthesized track claim naming it (§8)", + case: Case { claims: vec![text(&ai_track_claim(&ai_room(None)))], room: Some(ai_room(Some(Said { origin: 2, model: Some(label(MODEL)) }))), ..Default::default() }, + intent: Intent::Accept, + }, + Vector { + name: "certified", + about: "a review of a generated video at decode-equivalence, presented by a player whose decode path holds that tier and whose output is the reconstruction reviewed; the label shown at first exposure, after an ad break and within the interval (§9, §10)", + case: certified(&session("decode-equivalence", Some(video_out), &approved), &approved, Some(&label_policy(Some(30 * S), 500_000))), + intent: Intent::Accept, + }, + ]; + + // ---- refusals, each a control of one defect ---- + let mut refuse = |name: &'static str, about: &'static str, case: Case, at: At, reason: Reason| out.push(Vector { name, about, case, intent: Intent::Refuse(at, reason) }); + + let extra = with(photo.clone(), &["note"], json!("an extra member")); + refuse("record_malformed", "an origin claim with a member §3.1 does not list", case(&[&extra]), At::Claim(0), Reason::RecordMalformed); + let captured_with_input = signed(with(b.photo.clone(), &["inputs"], json!([input_of(&coded)])), CAMERA); + refuse( + "record_malformed_step", + "a capture that names an input: a capture has none (§3.2)", + case(&[&captured_with_input]), + At::Claim(0), + Reason::RecordMalformed, + ); + let mut flipped = photo.clone(); + let sig = flipped["sig"].as_str().expect("a signature").to_string(); + flipped["sig"] = json!(format!("{}{}", if &sig[..1] == "0" { "1" } else { "0" }, &sig[1..])); + refuse("bad_signature", "an origin claim whose signature has one digit changed", case(&[&flipped]), At::Claim(0), Reason::BadSignature); + let mut small = photo.clone(); + small["signer_pubkey"] = json!(format!("01{}", "00".repeat(31))); + refuse("bad_key", "an origin claim whose key is the identity point, of small order (SPEC §7 *Signatures* step 2)", case(&[&small]), At::Claim(0), Reason::BadKey); + let again = signed(with(b.photo.clone(), &["issued_at"], json!("2026-10-21T10:00:00Z")), CODEC); + refuse("claim_conflict", "two origin claims on one object", case(&[&photo, &again]), At::Claim(1), Reason::ClaimConflict); + let understated = signed(with(transcode_body(&o, &video), &["class"], json!("reconstructed")), CODEC); + refuse( + "class_mismatch", + "a transcode of a generated video that states the class reconstructed: a conversion keeps its input's class (§3.2)", + case(&[&video, &understated]), + At::Claim(1), + Reason::ClassMismatch, + ); + let wrong_source = signed(with(b.coded.clone(), &["source"], json!("digitalCreation")), CODEC); + refuse( + "source_mismatch", + "a codec's reconstruction of a capture that states digitalCreation: a conversion keeps its input's source (§5)", + case(&[&photo, &wrong_source]), + At::Claim(1), + Reason::SourceMismatch, + ); + let restated = with(input_of(&photo), &["class"], json!("reconstructed")); + let bad_input = signed(with(with(b.coded.clone(), &["inputs"], json!([restated])), &["class"], json!("reconstructed")), CODEC); + refuse( + "input_mismatch", + "a reconstruction that restates its input's class as reconstructed where the input's own claim says captured (§3.3)", + case(&[&photo, &bad_input]), + At::Claim(1), + Reason::InputMismatch, + ); + let labelled_captured = signed(with(b.photo.clone(), &["subject"], object_subject(&o.spectral)), CAMERA); + refuse( + "class_understated", + "a synthesized object labelled captured: the object is a generator's envelope (wai.generate.spectral, intent create), and its claim is a capture (§3.3)", + Case { claims: vec![text(&labelled_captured)], objects: vec![o.spectral.clone()], ..Default::default() }, + At::Claim(0), + Reason::ClassUnderstated, + ); + let unpinned = signed(with(b.up.clone(), &["evidence", "derivation"], json!("wai-synthesis:upscale3")), EDITOR); + refuse( + "evidence_invalid", + "replicate evidence naming a derivation §4 does not pin", + Case { claims: vec![text(&photo), text(&coded), text(&unpinned)], ..Default::default() }, + At::Claim(2), + Reason::EvidenceInvalid, + ); + let wrong_input = signed(with(b.up.clone(), &["evidence", "input_samples"], json!(hex(&sha(&[&o.photo_px])))), EDITOR); + refuse( + "input_samples_mismatch", + "an upscale whose evidence names the capture's samples, not those its input decodes to (§4.4)", + Case { claims: vec![text(&photo), text(&coded), text(&wrong_input)], decoded: chain_decoded.clone(), ..Default::default() }, + At::Claim(2), + Reason::InputSamplesMismatch, + ); + let mut sharpened = o.up_px.clone(); + sharpened[7] ^= 0x40; + let not_recomputed = signed(with(b.up.clone(), &["evidence", "output_samples"], json!(hex(&sha(&[&sharpened])))), EDITOR); + refuse( + "assistive_recompute_mismatch", + "an assistive claim whose recomputation differs: the delivered upscale has one sample altered after the derivation, and its evidence states the altered samples (§4.4)", + Case { + claims: vec![text(&photo), text(&coded), text(¬_recomputed)], + decoded: decoded(&[(&o.coded, &o.coded_px), (&o.upscaled, &sharpened)]), + ..Default::default() + }, + At::Claim(2), + Reason::AssistiveRecomputeMismatch, + ); + refuse( + "samples_mismatch", + "an upscale whose evidence recomputes, delivered as an object that decodes to other samples (§4.4)", + Case { + claims: vec![text(&photo), text(&coded), text(&up)], + decoded: decoded(&[(&o.coded, &o.coded_px), (&o.upscaled, &sharpened)]), + ..Default::default() + }, + At::Claim(2), + Reason::SamplesMismatch, + ); + refuse( + "assistive_not_proved", + "a float super-resolution step with attest evidence, under a policy that requires every assistive claim to be proved (§4.4, §11.1)", + Case { claims: [&photo, &coded, &sr].iter().map(|c| text(c)).collect(), policy: Policy { require_proof: true, ..Default::default() }, ..Default::default() }, + At::Claim(2), + Reason::AssistiveNotProved, + ); + refuse( + "claim_unknown", + "a marking whose origin claim the verifier is not given (§7.3)", + Case { claims: vec![text(&photo)], markings: vec![text(&marking(&video, None))], ..Default::default() }, + At::Marking(0), + Reason::ClaimUnknown, + ); + let mut lowered = payload_of(&video); + lowered[0] = Class::Captured.code(); + refuse( + "marking_mismatch", + "a marking whose payload carries the class captured for a synthesized claim (§7.2)", + Case { claims: vec![text(&video)], markings: vec![text(&marking(&video, Some(lowered)))], ..Default::default() }, + At::Marking(0), + Reason::MarkingMismatch, + ); + refuse( + "detection_mismatch", + "a detection under another scheme definition than the one the marking names (§7.3)", + Case { + claims: vec![text(&video)], + markings: vec![text(&marking(&video, None))], + detections: vec![text(&detection(&video["subject"]["content_hash"], Some(payload_of(&video)), b"another scheme definition"))], + ..Default::default() + }, + At::Detection(0), + Reason::DetectionMismatch, + ); + refuse( + "watermark_absent", + "a marked object in which the detector finds no mark: the second layer was stripped (§7.3)", + Case { + claims: vec![text(&video)], + markings: vec![text(&marking(&video, None))], + detections: vec![text(&detection(&video["subject"]["content_hash"], None, SCHEME_DOC))], + ..Default::default() + }, + At::Detection(0), + Reason::WatermarkAbsent, + ); + let mut foreign = payload_of(&video); + foreign[0] = Class::Synthesized.code(); + refuse( + "layers_disagree", + "the two marking layers disagree: the photo's claim says captured, and the detector reads from it a synthesized mark that points at another claim (§7.3)", + Case { claims: vec![text(&photo)], detections: vec![text(&detection(&photo["subject"]["content_hash"], Some(foreign), SCHEME_DOC))], ..Default::default() }, + At::Detection(0), + Reason::LayersDisagree, + ); + refuse( + "layer_missing", + "a synthesized object with no marking, under a policy that requires both layers (§7.1)", + Case { claims: vec![text(&video)], policy: Policy { dual_layer_required: true, ..Default::default() }, ..Default::default() }, + At::Claim(0), + Reason::LayerMissing, + ); + let r = ai_room(Some(Said { origin: 2, model: Some(label(MODEL)) })); + refuse( + "ai_track_unlabelled", + "an AI participant publishes speech with no synthesized track claim for it (§8.2)", + Case { room: Some(r), ..Default::default() }, + At::Room(room::At::Record { chain: 0, record: 2 }), + Reason::AiTrackUnlabelled, + ); + let r = ai_room(None); + refuse( + "ai_unmarked", + "an AI participant's chat that states no origin: the room refuses it (live-rooms §9.2 step 6)", + Case { claims: vec![text(&ai_track_claim(&r))], room: Some(r), ..Default::default() }, + At::Room(room::At::Record { chain: 2, record: 0 }), + Reason::Room(room::Reason::AiUnmarked), + ); + let mut broken = session("decode-equivalence", Some(video_out), &approved); + broken.entries[2] = signed(with(broken.entries[2].clone(), &["seq"], json!(3)), PLAYER); + refuse("player_chain_broken", "a player's log whose third entry claims seq 3 (§10.3)", certified(&broken, &approved, None), At::Player(2), Reason::PlayerChainBroken); + let mut l = Log::new(); + l.push(0, with(present(&video, "decode-equivalence", Some(video_out)), &["class"], json!("assistive"))); + refuse("present_mismatch", "a presentation that states the class assistive for a synthesized object (§10.3)", certified(&l, &approved, None), At::Player(0), Reason::PresentMismatch); + let mut l = Log::new(); + l.push(0, present(&video, "decode-equivalence", Some(label("another output")))); + refuse( + "present_output_irreproducible", + "a presentation whose stated output is not what the object decodes to (§9.2)", + certified(&l, &approved, None), + At::Player(0), + Reason::PresentOutputIrreproducible, + ); + let mut l = Log::new(); + l.push(0, present(&photo, "decode-equivalence", None)) + .push(S, json!({"type": "review", "subject": video["subject"]["content_hash"], "review": hex(&link_hash(&approved))})); + refuse( + "review_subject_mismatch", + "a review presented for an object the player has not presented (§9.2)", + Case { claims: vec![text(&video), text(&photo)], reviews: vec![text(&approved)], player: l.texts(), ..Default::default() }, + At::Player(1), + Reason::ReviewSubjectMismatch, + ); + let rejected = review(&video, &video_out, "decode-equivalence", "rejected"); + refuse( + "review_rejected", + "a rejected review presented as a review (§9.2)", + certified(&session("decode-equivalence", Some(video_out), &rejected), &rejected, None), + At::Player(2), + Reason::ReviewRejected, + ); + refuse( + "review_path_not_exact", + "a review presented on a non-exact path: the player decoded through a float path, tier none, and presents a review made at decode-equivalence (§9.2)", + certified(&session("none", Some(video_out), &approved), &approved, None), + At::Player(2), + Reason::ReviewPathNotExact, + ); + let other = review(&video, &label("another reconstruction"), "decode-equivalence", "approved"); + refuse( + "review_reconstruction_mismatch", + "a review over another reconstruction than the one the player presented (§9.2)", + certified(&session("decode-equivalence", Some(video_out), &other), &other, None), + At::Player(2), + Reason::ReviewReconstructionMismatch, + ); + let mut l = Log::new(); + l.push(0, shown(icon("LABEL_AI MODIFIED_black.svg"), "first-exposure")).push(0, present(&video, "decode-equivalence", Some(video_out))); + refuse( + "label_asset_mismatch", + "a label shown with another icon than the policy's (§10.4)", + certified(&l, &approved, Some(&label_policy(None, 500_000))), + At::Player(0), + Reason::LabelAssetMismatch, + ); + let mut l = Log::new(); + l.push(0, present(&video, "decode-equivalence", Some(video_out))).push(5 * S, shown(gen_icon, "first-exposure")); + refuse( + "label_not_shown", + "a label not shown at first exposure: the player's log shows the generated video presented at 0 s and the label first at 5 s, past the half-second grace (§10.4)", + certified(&l, &approved, Some(&label_policy(None, 500_000))), + At::Player(0), + Reason::LabelNotShown, + ); + let mut l = Log::new(); + l.push(0, shown(gen_icon, "first-exposure")).push(0, present(&video, "decode-equivalence", Some(video_out))).push(20 * S, resume()).push(20 * S, present(&video, "decode-equivalence", Some(video_out))); + refuse( + "label_not_shown_resume", + "no label after an ad break: presentation resumes at 20 s with no label shown since (§10.4)", + certified(&l, &approved, Some(&label_policy(None, 500_000))), + At::Player(3), + Reason::LabelNotShown, + ); + let mut l = Log::new(); + l.push(0, shown(gen_icon, "first-exposure")).push(0, present(&video, "decode-equivalence", Some(video_out))).push(45 * S, present(&video, "decode-equivalence", Some(video_out))); + refuse( + "label_not_shown_interval", + "no label within the policy's 30 s interval: the label last shown at 0 s, the video still presented at 45 s (§10.4)", + certified(&l, &approved, Some(&label_policy(Some(30 * S), 500_000))), + At::Player(2), + Reason::LabelNotShown, + ); + out +} + +fn transcode_body(o: &Objects, video: &Value) -> Value { + claim_body( + object_subject(&o.transcode), + "video", + "synthesized", + "convert", + "wai.video.hevc", + "trainedAlgorithmicMedia", + json!([]), + Value::Null, + json!([input_of(video)]), + Value::Null, + ) +} + +// ---- the file ---- + +fn case_json(c: &Case) -> Map { + let mut m = Map::new(); + m.insert( + "policy".into(), + json!({ + "dual_layer": if c.policy.dual_layer_required { "required" } else { "when-given" }, + "assistive": if c.policy.require_proof { "require-proof" } else { "accept-asserted" }, + }), + ); + m.insert("claims".into(), json!(c.claims)); + m.insert("markings".into(), json!(c.markings)); + m.insert("detections".into(), json!(c.detections)); + m.insert("reviews".into(), json!(c.reviews)); + m.insert("label_policy".into(), json!(c.label_policy)); + m.insert("player".into(), json!(c.player)); + m.insert("objects".into(), json!(c.objects.iter().map(|b| hex(b)).collect::>())); + m.insert("decoded".into(), json!(c.decoded.iter().map(|(h, b)| json!({"object": hex(h), "samples": hex(b)})).collect::>())); + m.insert( + "room".into(), + match &c.room { + None => Value::Null, + Some(r) => json!({ + "descriptor": hex(&r.descriptor), + "grants": r.grants.iter().map(|g| hex(g)).collect::>(), + "key_log": r.head, + "chains": r.chains.iter().map(|ch| ch.iter().map(|e| hex(e)).collect::>()).collect::>(), + }), + }, + ); + m +} + +fn outcome_json(o: &Outcome) -> Map { + let mut m = Map::new(); + match o { + Outcome::Refuse { at, reason } => { + m.insert("outcome".into(), json!("refuse")); + m.insert("reason".into(), json!(reason.code())); + m.insert("at".into(), at.to_json()); + } + Outcome::Accept(r) => { + m.insert("outcome".into(), json!("accept")); + m.insert( + "statements".into(), + json!({ + "claims": r.claims, + "detections": r.detections, + "ai_tracks": r.ai_tracks, + "reviews_presented": r.reviews_presented, + "labels": r.labels, + "visual_icon": r.visual_icon, + "room": r.room, + }), + ); + } + } + m +} + +fn case_from_json(v: &Value) -> Option { + let strs = |k: &str| -> Option> { v[k].as_array()?.iter().map(|x| x.as_str().map(String::from)).collect() }; + let room = match &v["room"] { + Value::Null => None, + r => Some(RoomInput { + descriptor: unhex(r["descriptor"].as_str()?)?, + grants: r["grants"].as_array()?.iter().map(|g| unhex(g.as_str()?)).collect::>()?, + head: r["key_log"].as_str().map(String::from), + chains: r["chains"].as_array()?.iter().map(|c| c.as_array()?.iter().map(|e| unhex(e.as_str()?)).collect::>>()).collect::>()?, + }), + }; + Some(Case { + claims: strs("claims")?, + markings: strs("markings")?, + detections: strs("detections")?, + reviews: strs("reviews")?, + label_policy: v["label_policy"].as_str().map(String::from), + player: strs("player")?, + objects: v["objects"].as_array()?.iter().map(|x| unhex(x.as_str()?)).collect::>()?, + decoded: v["decoded"] + .as_array()? + .iter() + .map(|d| Some((unhex(d["object"].as_str()?)?.try_into().ok()?, unhex(d["samples"].as_str()?)?))) + .collect::>()?, + room, + policy: Policy { dual_layer_required: v["policy"]["dual_layer"] == "required", require_proof: v["policy"]["assistive"] == "require-proof" }, + }) +} + +/// The corpus: `vectors.json`. +pub fn generate() -> Vec<(String, String)> { + let vs = vectors(); + let mut seen = BTreeSet::new(); + let mut cases = Vec::new(); + for v in &vs { + let got = super::verify(&v.case); + match (&v.intent, &got) { + (Intent::Accept, Outcome::Accept(_)) => {} + (Intent::Refuse(at, r), Outcome::Refuse { at: a, reason }) if at == a && r == reason => { + seen.insert(*r); + } + _ => panic!("vector {} does not reach the outcome it was written to have: {got:?}", v.name), + } + let mut m = Map::new(); + m.insert("name".into(), json!(v.name)); + m.insert("about".into(), json!(v.about)); + m.extend(case_json(&v.case)); + m.extend(outcome_json(&got)); + cases.push(Value::Object(m)); + } + for r in Reason::ALL { + assert!(seen.contains(&r), "no vector of the corpus is refused with {r}"); + } + let doc = json!({ + "about": "the conformance corpus of extensions/synthesis.md §13, written by wai_synthesis_vectors gen", + "eu_icons": EU_ICONS.iter().map(|(n, d)| json!({"file": n, "sha256": d})).collect::>(), + "vectors": cases, + }); + vec![("vectors.json".into(), serde_json::to_string_pretty(&doc).expect("JSON") + "\n")] +} + +/// Check the corpus's files: every case gets the outcome and the statements it states. +pub fn verify(files: &BTreeMap) -> Vec { + let mut fails = Vec::new(); + let Some(doc) = files.get("vectors.json").and_then(|s| serde_json::from_str::(s).ok()) else { + return vec!["vectors.json does not read".into()]; + }; + for v in doc["vectors"].as_array().into_iter().flatten() { + let name = v["name"].as_str().unwrap_or("?"); + let Some(c) = case_from_json(v) else { + fails.push(format!("{name}: the case does not read")); + continue; + }; + let got = Value::Object(outcome_json(&verify_case(&c))); + let mut want = Map::new(); + for k in ["outcome", "reason", "at", "statements"] { + if let Some(x) = v.get(k) { + want.insert(k.into(), x.clone()); + } + } + if got != Value::Object(want) { + fails.push(format!("{name}: {got}")); + } + } + fails +} + +fn verify_case(c: &Case) -> Outcome { + super::verify(c) +} diff --git a/wai/wai-rs/tests/live_room.rs b/wai/wai-rs/tests/live_room.rs index 00da60711..213ced596 100644 --- a/wai/wai-rs/tests/live_room.rs +++ b/wai/wai-rs/tests/live_room.rs @@ -39,7 +39,7 @@ fn the_corpus_covers_every_reason_and_room() { for r in Reason::ALL { assert!(reasons.contains(r.code()), "nothing in the corpus is refused with {r}"); } - for name in ["lesson", "broadcast", "takeover", "redundant_failover", "promote_viewer", "dvr"] { + for name in ["lesson", "broadcast", "takeover", "redundant_failover", "promote_viewer", "dvr", "ai_participant"] { let v = vectors.iter().find(|v| v["name"] == name).unwrap_or_else(|| panic!("no vector {name}")); assert_eq!(v["outcome"], "accept", "{name}"); } diff --git a/wai/wai-rs/tests/synthesis.rs b/wai/wai-rs/tests/synthesis.rs new file mode 100644 index 000000000..73354cfc0 --- /dev/null +++ b/wai/wai-rs/tests/synthesis.rs @@ -0,0 +1,71 @@ +//! `wai.media.synthesis` (extensions/synthesis.md): the committed corpus +//! `live-conformance/synthesis/` regenerates byte for byte from its fixed seeds, holds no +//! other file, every case in it gets the outcome it states, and the extension's §12 lists +//! exactly the reasons the reference reports. The generator itself asserts that the corpus +//! gives every one of them. +#![cfg(feature = "synthesis")] + +use std::collections::BTreeMap; +use std::path::PathBuf; +use wai::synthesis::{corpus, Reason}; + +fn wai_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") +} + +fn committed() -> BTreeMap { + let dir = wai_dir().join("live-conformance/synthesis"); + std::fs::read_dir(&dir) + .expect("the committed corpus") + .flatten() + .map(|e| (e.file_name().to_string_lossy().into_owned(), e.path())) + .filter(|(n, _)| n != "README.md") + .map(|(n, p)| (n, std::fs::read_to_string(p).expect("a text file"))) + .collect() +} + +#[test] +fn the_committed_corpus_regenerates_from_its_seeds() { + let files = committed(); + let generated: BTreeMap = corpus::generate().into_iter().collect(); + let extra: Vec<&String> = files.keys().filter(|k| !generated.contains_key(*k)).collect(); + let missing: Vec<&String> = generated.keys().filter(|k| !files.contains_key(*k)).collect(); + let differ: Vec<&String> = generated.iter().filter(|(k, v)| files.get(*k).is_some_and(|f| f != *v)).map(|(k, _)| k).collect(); + assert!(extra.is_empty() && missing.is_empty() && differ.is_empty(), "extra {extra:?}, missing {missing:?}, differ {differ:?}: run `wai_synthesis_vectors gen`"); +} + +#[test] +fn every_case_gets_its_stated_outcome() { + let fails = corpus::verify(&committed()); + assert!(fails.is_empty(), "{}", fails.join("\n")); +} + +/// §12 of the extension lists exactly the codes `Reason::ALL` has, each once, in order. +#[test] +fn the_extension_lists_the_reasons() { + let text = std::fs::read_to_string(wai_dir().join("extensions/synthesis.md")).expect("the extension"); + let at = text.find("\n## 12. Reasons").expect("a §12"); + let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); + let listed: Vec = + text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect(); + let want: Vec = Reason::ALL.iter().map(|r| r.code().to_string()).collect(); + assert_eq!(listed, want, "§12's table and Reason::ALL differ"); +} + +/// §10.2's table pins exactly the icons `EU_ICONS` holds, each by its SHA-256, in order. +#[test] +fn the_extension_pins_the_icons() { + let text = std::fs::read_to_string(wai_dir().join("extensions/synthesis.md")).expect("the extension"); + let at = text.find("\n### 10.2 ").expect("a §10.2"); + let end = text[at + 1..].find("\n### ").map_or(text.len(), |e| at + 1 + e); + let pinned: Vec<(String, String)> = text[at..end] + .lines() + .filter(|l| l.starts_with("| `LABEL_AI")) + .filter_map(|l| { + let cells: Vec<&str> = l.split('`').collect(); + Some((cells.get(1)?.to_string(), cells.get(3)?.to_string())) + }) + .collect(); + let want: Vec<(String, String)> = wai::synthesis::EU_ICONS.iter().map(|(n, d)| (n.to_string(), d.to_string())).collect(); + assert_eq!(pinned, want, "§10.2's table and EU_ICONS differ"); +}