open-standards/wai/extensions/detection.md
Transaction Science 1ff71887ff
Some checks failed
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 30s
wai-audit collector and player integration / does this change touch it (push) Successful in 24s
wai-app and Create / wai-app · does this change touch it (push) Successful in 21s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 28s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 33s
wai-key-proxy / does this change touch it (push) Successful in 27s
wai-fleet service / does this change touch it (push) Successful in 16s
WAI operation catalogue / does this change touch it (push) Successful in 24s
wai-ledger service / does this change touch it (push) Successful in 26s
wai-quantum for JavaScript / does this change touch it (push) Successful in 26s
wai-sink-android native sink library / does this change touch it (push) Successful in 32s
WAI Studio / does this change touch it (push) Successful in 32s
wai-quantum studio and desktop / does this change touch it (push) Successful in 44s
wai-sink-swift native sink package / does this change touch it (push) Successful in 34s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 2m16s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Failing after 7m31s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 6m13s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 9m1s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 23s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 17s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 57s
wai-audit collector and player integration / collector · linux (push) Successful in 2m24s
wai-key-proxy / proxy · linux (push) Successful in 2m32s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m42s
wai-ledger service / service · linux (push) Successful in 3m21s
wai-fleet service / service · linux (push) Successful in 5m24s
wai-quantum for JavaScript / javascript · linux (push) Successful in 5m33s
wai-quantum studio and desktop / studio · browser (push) Successful in 5m13s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 9m58s
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Successful in 12m36s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 13m27s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 11m10s
WAI Studio / Studio in WebKit · linux (push) Successful in 13m50s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 47s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 1m55s
wai-fleet service / service · macos (push) Successful in 1m5s
wai-ledger service / service · macos (push) Successful in 1m38s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 8m20s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 8m5s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 16m45s
wai-quantum studio and desktop / desktop app · macos (push) Failing after 12m13s
WAI Studio / Studio in Chromium and Firefox · macos (push) Failing after 12m25s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 17m39s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 2m42s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 7m51s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 8m29s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Successful in 9m9s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 18m57s
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 7m7s
C2PA stores carry identity assertions, soft bindings and the ISCC identifier, trust lists are pinned by digest, and a detection verdict is a claim of its own
c2pa-binding §4.8 to §4.10 and §6.4: after its three assertions a store of wai.asset.c2pa may
carry wai-mark:tent64 as a C2PA soft-binding assertion naming its robustness claim, the
envelope's ISCC identifier, and the Creator Assertions Working Group's identity assertions
(1.3, cawg.x509.cose); a verifier may hold its anchors as trust lists in the C2PA Trust List's
form, pinned by the SHA-256 of their bytes. The algorithm-list entries for tent64 and band64
are written in extensions/c2pa-soft-binding-entries.json.

extensions/iscc.md: ISO 24138's codec, ISCC-SEQ, Instance-, Image- and Audio-Codes and the
ISCC-CODE from the IEP texts, replaying the public test vectors; WAI's integer pre-processing.

extensions/detection.md: wai.media.detection, a detector's signed verdict at an operating point
named by digest, its evidence a robustness record of binding type detector; refused as
provenance, never a change to an origin claim's class.

Corpora c2pa-identity/, iscc/ and detection/, each with a second implementation in Python.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-10 15:25:49 -05:00

15 KiB
Raw Permalink Blame History

WAI Extension: Detection Verdicts

Status: Draft. Registers the record wai.media.detection: a synthetic-media detector's signed verdict over one object, a claim of the detector's operator and of nobody else. A verdict names the detector at its operating point — its term, the SHA-256 of its model or program, and its threshold — by the digest of that definition; states the detector's score for the object and its threshold as exact fractions, and the verdict the score gives at the threshold; and names the robustness record (robustness-evidence §5, its binding type detector) that measures the detector at that operating point. A verifier checks a verdict's arithmetic, its signature and its evidence. A verdict is never provenance: it is refused where it is presented as an origin claim (synthesis §3.1), as the record that derived a subject, as an input's origin, or as an assertion of a C2PA manifest (c2pa-binding §6.2 step 4); and a verifier states it beside the origin claims of the case, whose classes it never raises or lowers. No new transport. The reference implementation is the detection module of wai-rs (feature detection); the corpus is live-conformance/detection/, and its second implementation is live-conformance/detection_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Texts read for this revision, each pinned to the version named: the C2PA Technical Specification, version 2.5 (October 2026), §2.3.9 and §2.3.10 (provenance and provenance data) and §16.3.7 (soft bindings, which a detector is not); and the Code of Practice on Transparency of AI-Generated Content as synthesis pins it, Section 1 Measures 2.1 and 2.3, which distinguish a detection resting on a watermark from one that does not.

1. Scope

In scope: the record's encodings (§2); the verdict (§3); the evidence it names (§4); verification (§5); why a verdict is never provenance (§6); the reasons (§7); and the corpus (§8).

Out of scope: any detector: this extension names a detector by digest and says nothing of how it works or how good it is, which its robustness records state; a watermark's detection, which synthesis §7.3 records as wai-detection and checks against the claim the mark's payload names; what a party does with a verdict; and a verdict over a group, a track or a span.

2. Records

A verdict is a JSON object under the encodings and the reading of synthesis §2 — byte strings in lowercase hexadecimal, unsigned integers of at most 2^53 − 1, no other number, no boolean, strict reading of RFC 8259, exactly the members §3 lists, and a kind naming it; otherwise record_malformed — with one exception: a rate member is read after every other member, and one that holds anything but an exact fraction is rate_not_exact. A rate is [n, d], two integers with 1 ≤ d and n ≤ d, in lowest terms (robustness-evidence §7). It is signed:

sig       = Ed25519(signer_key, "wai:detection\x01" || JCS(record without "sig"))
link_hash = BLAKE3("wai:detection-link\x01" || JCS(record))

JCS is RFC 8785. The signature is verified by WAI's strict rule (SPEC §7 Signatures): a key that fails step 1 or 2 is bad_key, a signature that fails a later step bad_signature. A verdict is named by its link hash. Its issued_at is a date-time of jwp-receipts §2.4.3, self-asserted.

3. The verdict

kind "wai-detection-verdict", signed by the party that ran the detector:

member type meaning
kind string "wai-detection-verdict"
subject 32 B hex the content hash of the object examined (jwp-receipts §2.1)
media string image, video, audio or text
detector term the detector's name
model 32 B hex the SHA-256 of the detector's model or program
threshold rate the score at and above which the detector gives synthetic
detector_digest 32 B hex the digest of the detector's definition at this operating point, below
score rate the detector's score for the object
verdict string synthetic where score ≥ threshold, not-detected otherwise
robustness 32 B hex or null the link hash of the robustness record that measures the detector at this operating point (§4), or null where none is named
issued_at date-time when it signed
signer_pubkey, sig §2

The definition of a detector at an operating point is the JSON object {"detector": D, "media": M, "model": H, "threshold": T} of the verdict's members of those names, and its digest is the SHA-256 of its JCS. A verdict whose detector_digest is another is detector_digest_mismatch: the digest names the detector and its threshold together, so a verdict cannot move its threshold and keep its evidence. A score is a fraction the detector's operator states; a verifier compares it with the threshold exactly, and a verdict that is not the one they give is verdict_inconsistent.

4. The evidence

A detector's evidence is a robustness record (robustness-evidence §5) with binding_type detector, whose algorithm is the detector's term, whose algorithm_digest is the verdict's detector_digest, and whose media is the verdict's: it measures the detector at that operating point. Robustness-evidence §5 reads it as it reads any record: its corpus's items are content the detector is meant to flag, each value the one byte 01; an item survives an attack where the detector, run on it attacked, gives synthetic; its negatives are content the detector is meant not to flag, and a false positive is one it flags, attacked. A verifier that does not run the detector reads the record as stated (robustness-evidence §5 check 8), its table its signer's statement, checked for its arithmetic and its signature. A verdict whose robustness names a record the verifier does not hold, verified, is robustness_not_held; one whose record is of another detector, digest, binding type or media is robustness_mismatch.

5. Verification

5.1 Inputs

A verifier is given a case: the origin claims presented as the provenance of the case's objects (synthesis §3.1), the robustness records and claims it was given (robustness-evidence §9.1), and the verdicts, each a list of texts in order; and its policy, evidence: required, every verdict names a record, or when-given.

5.2 Steps

The verifier runs these steps and stops at the first refusal, which it reports with the reason, the list and the index in it.

  1. The provenance. Each text of the provenance list, in order, is an origin claim: one whose kind is a verdict's is verdict_as_provenance; one that does not read as synthesis §2 and §3.1 read an origin claim, or whose signature does not verify, is provenance_invalid.
  2. The evidence. The robustness list verifies as robustness-evidence §9.2 has it, under the policy stated: otherwise robustness_invalid, at the record that failed.
  3. The verdicts, in order: each reads and its signature verifies (§2, §3: record_malformed, rate_not_exact, bad_key, bad_signature); its detector_digest is its definition's (detector_digest_mismatch); its verdict is its score's at its threshold (verdict_inconsistent); and its evidence (§4): null under evidence required, or a record not held, is robustness_not_held, and a record of another operating point robustness_mismatch.
  4. No verdict as provenance. No origin claim names a verdict's link hash as its record, the record that derived its subject, or as an input's origin, the claim of an input: otherwise verdict_as_provenance, at that claim.

A case every step accepts is accepted. This verifier reads the origin claims for their subjects and classes; verifying them as provenance — their classes against their inputs and steps, their evidence, their marks — is synthesis §11's.

5.3 What an accepted case states

  • for each origin claim of an object, in order: its link hash, its object, and its class, as the claim signs it;
  • for each verdict, in order: its link hash, its object, its detector, its verdict, the robustness record it names or null, and where it stands beside the claim of the same object: agrees where the verdict is synthetic and the claim's class synthesized, or the verdict not-detected and the class another; disagrees otherwise; no-claim where the case holds no claim of the object.

The classes are the claims' own, whatever the verdicts: a case states the same classes with its verdicts as without them.

6. Never provenance

Provenance is what the parties that made a piece of content state about how they made it, each signing for its own step (synthesis §3.1; C2PA 2.5 §2.3.9). A detector made nothing: its verdict is an inference by a third party from the content alone, and a classifier's inference is a different kind of statement from a signed account of a step. So a verdict:

  • is never an origin claim, nor the record that derived a subject, nor an input's claim (§5.2 steps 1 and 4);
  • is never an assertion of a C2PA manifest: a store holding one, labelled science.transaction.wai.detection, is refused (c2pa-binding §6.2 step 4, detection_as_provenance), since the manifest's assertions are its claim signer's statements;
  • never raises or lowers an origin claim's class (§5.3). A synthetic verdict over a claim of captured content disagrees with it, and the claim stays captured; a not-detected verdict over a claim of synthesized content disagrees with it, and the claim stays synthesized. Synthesis §3.2 raises a class only along a chain of signed steps, and a verdict is not a step;
  • is not a soft binding: a soft binding is a value computed from the content to find its provenance again (C2PA 2.5 §16.3.7), and a verdict is a judgement about the content.

A party that relies on a verdict relies on the detector's operator and on the detector's measured robustness, by its policy; a verifier states the verdict beside the claims so that neither is mistaken for the other.

7. Reasons

reason raised at
record_malformed §2, §3, §5.2 step 3
rate_not_exact §2, §5.2 step 3
bad_key §2, §5.2 step 3
bad_signature §2, §5.2 step 3
provenance_invalid §5.2 step 1
verdict_as_provenance §5.2 steps 1 and 4, §6
robustness_invalid §5.2 step 2
detector_digest_mismatch §3, §5.2 step 3
verdict_inconsistent §3, §5.2 step 3
robustness_not_held §4, §5.2 step 3
robustness_mismatch §4, §5.2 step 3

8. Conformance

live-conformance/detection/ holds the corpus, written from fixed seeds by wai_detection_vectors gen: vectors.json, and each origin claim, robustness record and verdict a file of its own. Every key is derived from the seeds, and none is a key anyone holds. The detector is the corpus's stand-in, wai-detector:conformance: it scores a picture by the share of its pixels whose grey, (77 R + 150 G + 29 B + 128) >> 8, is at least 128, its model the SHA-256 of the text of that definition the corpus states, its threshold [1, 2]. It detects nothing about how a picture was made; it stands for a detector a verifier does not implement. Its robustness records are measured: the generator runs it, at the threshold [1, 2] and at [3, 4], over two bright pictures as items and two dark ones as negatives under four attacks of robustness-evidence §4.

  • Accepted cases: four objects whose claims say captured, synthesized, captured and synthesized, and a verdict over each — two that agree with their claims and two that disagree, the classes stated as signed; the same claims with no verdict, stating the same classes; and a verdict over an object no claim names.
  • Refused cases, each a control of one defect: a verdict whose signature is altered, one under a key that does not decode, one with a member §3 does not list, one whose score is not in lowest terms, one whose threshold is not its definition's, one whose verdict is not its score's, one that names a record the verifier does not hold, one that names none under the policy that requires one, one that names the record of another threshold, a robustness record that does not verify, an origin claim that does not read and one whose signature is not its signer's, a verdict in the place of an origin claim, an origin claim that names a verdict as the record that derived its object, and one that names a verdict as its input's claim. Every reason of §7 refuses one.

A conforming verifier gives every case the outcome, the reason, the list and the index, and for an accepted case what §5.3 states, as the corpus states them. wai_detection_vectors verify and the test tests/detection.rs check that every file regenerates byte for byte, that no other file is there, that wai-rs gives every case its outcome, that every reason of §7 refuses a case, that the case with verdicts states the classes the case without them states, and that §7 lists exactly the reference's codes. live-conformance/detection_verify.py is the second implementation, written from this text: it shares no code with wai-rs; it verifies signatures with WAI's strict rule (claim-conformance/_ed25519.py), hashes with BLAKE3 from claim-conformance/_blake3.py, verifies the robustness records with robustness-evidence's second implementation, gives every case the same outcome, mutates copies of the accepted cases and requires every copy to be refused, and drops each of its own checks in turn and requires the corpus to catch it.

9. Security and privacy considerations

  • A verdict is an opinion. It says what a detector, at a threshold its operator chose, made of the content; detectors miss content and flag content, and their rates move with the content and the attack. The robustness record says how this detector did on the corpus it names, under the attacks it names, and no more (robustness-evidence §14).
  • Disagreement is information. A verdict that disagrees with a signed claim is reported, not resolved: the claim is its signer's statement, attributable to that signer, and the verdict the detector operator's. A relying party that weighs them does so by its own policy.
  • Thresholds. A verdict's threshold is in its signed definition, so an operator cannot report a score against a threshold other than the one its evidence measured.
  • Privacy. A verdict names an object by its content hash and its detector by digest; it names no viewer. A verdict about a person's content is a statement about that content made by a third party, and its publication is that party's act.