wai-mark:tent64: the second marking layer, a watermark recomputed from its payload, read with no key, measured against an attack battery (SYN-2) #407

Merged
dcharlot merged 10 commits from wai/synthesis-mark into main 2026-10-22 00:11:35 -04:00
Owner

What this is

The second marking layer of wai.media.synthesis, made real. extensions/synthesis.md §7.4 left the watermark scheme as an empty slot (wai-mark:<name> + digest), so a detection was a signed statement and nothing more. This PR registers a scheme, wai-mark:tent64, and ties §7.3's agreement steps to it, adds the detection signpost of the Code of Practice's Measure 3.4(c)(ii), and measures what the mark survives.

Texts read for it, fetched today: the Code of Practice on Transparency of AI-Generated Content, final text of 10 June 2026 (the Commission's document 129555, SHA-256 7bd22c5a…bf9bc28): Section 1 Measures 1.1, 2.1, 2.3, 3.2 to 3.4 and the Glossary's signpost solution; C2PA 2.4 §9.3, §18.10, §18.15.4.8, §18.15.5 (soft binding, c2pa.watermarked.bound); RFC 8615.

The scheme (§7.4, §7.5)

  • Frames and grid. RGB8 frames of at least 128 × 128. Each frame is cut into a 64 × 64 grid by the edges ⌊i·W/64⌋ and ⌊j·H/64⌋, so the grid follows the picture through a rescale or a change of aspect.
  • Pattern. In each cell, a separable integer tent checkerboard ω. It is zero-sum, so a brightness change leaves a cell's statistic s = Σ ω·Y unchanged and a contrast change only scales it.
  • Layout. A public SHA-256 stream gives a shuffle of the 4096 cells, a sign for each cell, and a mask over the code bits. Each of the 248 code bits is carried by 16 cells scattered over the frame; 128 cells carry nothing.
  • Code. BCH(255, 71, 59) over GF(2^8), shortened to (248, 64). It corrects 29 errors and is decoded only within that radius. The generator polynomial is printed in hex and derived from its definition by both implementations.
  • Embedding. Informed and bounded: each bit gets the least amplitude a ≤ 12 at which 2·σ·T ≥ 3·Q, where T is the bit's statistic and Q its weight mass. The same delta goes to R, G and B, clamped. A bit the host already satisfies is left untouched. All integer arithmetic.
  • Video. The whole payload is repeated in every frame, and the detector sums the statistics over the frames. Reason: any single frame, a clip of any length, a clip with dropped or re-timed frames, or a still taken from the video carries the payload, and there is no position in time to recover.
  • Detection. Key-free. The detector tries the 8 quarter turns and mirrors in a fixed order. In each, it reads signs, unmasks, decodes, and accepts the first payload whose class byte is ≤ 3.

What it guarantees (§7.5.6)

  • No hidden channel. The marked output is a pure function of the unmarked frames and the 8-byte payload: no key, no seed, no embedder choice. A verifier holding the unmarked frames recomputes the marked ones. If the digests are equal, the mark carries exactly the payload and nothing else, so no viewer or session identifier can ride on it. The text contrasts this with forensic-watermark.md, whose purpose is the opposite.
  • False-positive bound. Derived as 8 · 2^58 · V(248, 29) / 2^248 = 2^−61.39 (about 3.3·10^−19), the 8 being the orientations. It was checked on unmarked pictures:
    • the words read from the 32 measurement pictures in 8 orientations have ones with mean 124.33 and sd 7.85;
    • the 31,744 cross-picture pairs differ in a mean of 123.91 bits, sd 7.95, minimum 91;
    • the uniform model predicts 124 and 7.87 for both.
    • No unmarked picture under any attack gave a payload, and the photograph set agrees.
  • PSNR floor. The PSNR of full-amplitude embedding for the frame's size: 36.66 dB at 512², 38.73 dB at 256², 35.76 dB at 768 × 512. The absolute lower bound over all admitted sizes is 26.55 dB.

Robustness, measured (§7.5.7, §7.5.8)

The battery is 33 picture attacks and 7 clip attacks. Each is an integer function, with noise drawn from a fixed SHA-256 stream, so a run reproduces bit for bit. Python re-runs every picture attack on the corpus still and gets identical digests. The attacks are:

  • re-encoding with the crate's own wai.video.int_motion encoder at q = 4 to 48, and a staged base layer (half resolution, int_motion, then wai-synthesis:upscale2);
  • denoise3 once and four times;
  • area resampling at 3/4, 1/2, 1/3, 1/4 and 3/2, plus upscale2 and an aspect change;
  • crops of 1%, 2%, 5% and 10%;
  • mirror and a 90° turn;
  • integer bilinear rotations of 0.5°, 1° and 2°;
  • uniform noise of ±4 and ±16;
  • brightness ±32 and contrast ×1/2 and ×3/2;
  • a local blur over a quarter of the area;
  • for clips: inter coding, frame drop, one frame, scale and noise.

The measurement sets are generated in-repo from fixed seeds: 16 spectral pictures (wai.generate.spectral with its committed model), 16 dead-leaves pictures, and 4 panning clips. The informative photographs column covers 24 lossless 768 × 512 photographs of a common test suite. They are measured with wai_synthesis_mark_vectors measure, named by SHA-256 in the corpus README and not committed.

Claims rule: an attack is claimed only if every picture in every column was detected with its payload and no unmarked picture under it gave a payload.

  • Claimed for pictures: int-motion q4/8/16/32/48, staged-base-q8, denoise3 (×1 and ×4), scale 3/4, 1/2 and 3/2, upscale2, aspect-3/4, half-then-int-motion-q16, mirror, rotate-90, noise ±4/±16, brightness ±32, contrast 1/2 and 3/2, local-blur.
  • Claimed for clips: all clip attacks.
  • Not claimed: any crop (2% already fails everywhere), any small rotation (1° fails everywhere), scale 1/3 (fails), and scale 1/4 (1 of 24 photographs lost).
  • The spec says plainly that the detector does no shift or angle search, that key-free means anyone can find and remove the mark or forge a payload into other content, and that no adversarial resistance is claimed (§14).
  • Imperceptibility: mean PSNR 45.0 dB (spectral), 43.7 dB (leaves), 42.1 dB (clips), 43.6 dB (photographs); SSIM 0.97–0.98, clips 0.95.

Verification (§7.3, §7.6, §7.8)

  • Record changes. A marking gains embedding (frame count and size, plus SHA-256 of the unmarked and marked frames). A detection gains read (frame count and size, plus SHA-256 of the frames read).
  • Marking checks. With the unmarked frames held, the verifier recomputes (mark_input_mismatch, mark_recompute_mismatch). With the decoded frames held, it compares (samples_mismatch).
  • Detection checks. With the frames held, the detector is run again (detection_irreproducible). This is where "an attack beyond the claimed robustness reported as detected-and-agreed" is refused.
  • Agreement by inheritance. A conversion that does not re-mark its output carries its input's mark, and the detection agrees with the conversion's claim through its inputs (agree-inherited). A capture claim over marked synthesized content is layers_disagree.
  • New policy member. second_layer: require-proof (layer_not_proved).
  • Other new codes. scheme_mismatch and mark_evidence_invalid.
  • Sinks that generate (§7.8). A sink that generates marks as part of the reconstruction its tier holds. A player that presents the unmarked frames gets present_output_irreproducible.
  • wai-mark:conformance is kept. It is the scheme with no algorithm. It exercises the records independent of any algorithm and stands for any scheme a verifier does not implement.

Signpost and resolver (§7.7)

  • In the content. The openly disclosed mark itself. Its payload points at the claim, whose markings name any other schemes.
  • In the metadata. Each marking's C2PA statement: c2pa.watermarked.bound plus a c2pa.soft-binding assertion with alg = term, alg-params = digest and the payload as the block value. The marking's statement is part of §11.3's accepted output.
  • Resolver. A proposed /.well-known/wai-mark/<name> (the definition) and /.well-known/wai-mark/<name>/payload/<p> (claims by payload).

Deliverables

  • Spec: wai/extensions/synthesis.md §7 rewritten (7.1–7.8), with matching updates to the header, §1, §11, §12 (6 new codes), §13 and §14.
  • Reference:
    • wai-rs synthesis::mark, under feature synthesis because the verifier needs it;
    • the synthesis_mark module (battery, measurement sets, PSNR/SSIM, corpus), under the new feature synthesis_mark = synthesis + video_int + generate + png_still, which adds no dependency;
    • the wai_synthesis_mark_vectors gen|verify|measure binary.
  • Corpus: wai/live-conformance/synthesis-mark/, with scheme.json, vectors.json, battery.json and frames/<sha256>.rgb (about 1.3 MB). It contains:
    • codewords, and decodes with 0, 1, 29, 30 and 40 bits flipped;
    • 3 embeddings, with per-bit amplitudes;
    • 8 detections;
    • a 66-entry battery sample;
    • 16 cases, including the negative controls the brief asked for: a mark with the wrong payload, a marked output that does not recompute (one extra bit, a hidden channel), a detection claiming a payload the content does not carry, and an attack beyond the claimed robustness that is not reported as agreeing.
  • Synthesis corpus: regenerated for the new members and statements, with 3 new refusal vectors.
  • Second implementation:
    • live-conformance/_wai_mark.py (scheme and battery, written from §7.5);
    • synthesis_verify.py, with the §7.6 checks;
    • synthesis_mark_verify.py, which checks the corpus, the spec's definition, generator, figures, table and claims, applies 14 mutations, and weakens its own verifier 3 ways.
  • Housekeeping: CI step in wai-conformance.yml; REGISTRY wire identifiers (wai-mark:tent64 and wai-mark:conformance as schemes, /.well-known/wai-mark/ as proposed); regenerated registry export; SPEC §5 paragraph; CHANGELOG; live-conformance/README.md; the corpus README with its Provenance line; the int_kernels_vectors.rs live-conformance reason extended.

Checks

All were run on the merged tree (origin/main at aa0b98d33, merged as 5d33f01b4). Cargo commands ran on the build host through rb; Python and the scripts ran locally.

check command result
registry export cargo run --no-default-features --bin wai -- registry export --check (after registry export) up to date
scheme and battery unit tests cargo test --no-default-features --features synthesis_mark --lib -- synthesis::mark synthesis_mark:: 13 passed
scheme corpus cargo test --release --no-default-features --features synthesis_mark --test synthesis_mark 5 passed (46 s): regenerates byte for byte, every vector and case, definition/generator/shuffle pinned, claims and figures checked against battery.json
synthesis unit tests cargo test --no-default-features --features synthesis --lib synthesis:: 17 passed
synthesis corpus cargo test --no-default-features --features synthesis --test synthesis 4 passed
registry cargo test --no-default-features --lib codecs::registry 14 passed (wire identifiers written in their sections)
provenance and CLI cargo test --no-default-features --test carriage --test manifest_builders --test cli_wrap_intent --test cli_registry --test corpus_provenance all passed
sha256 rule cargo test --release --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int --test int_kernels_vectors 8 passed
clippy cargo clippy --no-default-features --features synthesis_mark --all-targets no notes in any file this PR touches; the remaining notes are pre-existing (jpegai_*)
second implementation, scheme python3 -I live-conformance/synthesis_mark_verify.py live-conformance/synthesis-mark PASS: 14 mutations caught, 3 weakened verifiers caught
second implementation, synthesis python3 -I live-conformance/synthesis_verify.py live-conformance/synthesis PASS, 41 vectors
neighbouring verifiers live_rooms_verify.py rooms, live_events_verify.py events PASS
repo scripts check-tests-in-ci.py, check-workflow-concurrency.sh, check-spec-refs.py, check-wai-section-refs.py, check-no-vanity-metrics.sh, test-public-text-checks.sh, check-comparative-prose.sh, check-orphan-sources.py all clean
public names check-public-names.sh with the private denylist every file this PR touches is clean, as are this body and the commit messages; a full-tree run reports 9 lines in wai-rs/src/live_provenance.rs, which this PR does not touch (pre-existing)
dependents and lockfiles no new crate dependency; synthesis_mark composes existing features; no code outside wai-rs uses synthesis nothing to rebuild; no Cargo.lock change

Falsifications

Each was committed first, then run on the build host: the patch was applied, the guarding check run, and the file restored.

rule broken check exit
marking recomputation dropped (Rust) tests/synthesis_mark every_vector_and_case 101
detection re-run dropped (Rust) same 101
agreement by inheritance dropped (Rust) same 101
spec claims crop-1% the_extension_claims_only_what_the_measurement_shows 101
spec figure 124.33 changed to 124.34 same 101
reference amplitude 12 changed to 11 the_extension_pins_the_scheme 101
reference margin changed to 1/1, corpus not regenerated the_committed_corpus_regenerates_from_its_seeds 101
Python margin changed to 1/1 synthesis_mark_verify.py 1
Python decoder radius changed to 28 same 1
Python luma weights changed same 1
Python delta rounded half down same 1
Python spec claims crop-1% same 1
Python §7.6 recomputation skipped same 1

Two attempts did not fail, and the reasons are worth recording:

  • My first "spec claims crop-1%" patch matched across a line break and was never applied. The harness now aborts when a patch does not apply, and both re-runs fail as shown above.
  • Flipping the tent's sign is a true symmetry, not a defect: ω is the product of two tents, so negating both leaves it unchanged. I replaced that falsification with the luma and rounding ones.

Not done

  • Audio (the stretch goal) is not reached, and the spec says so (§1). Text has no scheme.
  • No crop or rotation resynchronisation. The scheme states these as not survived instead.
  • Photograph licence. The photographs are measured locally and not committed. The page hosting them gives no formal licence; it says the hoster understands them to be released for unrestricted usage. The informative column could be dropped if the owner prefers.
  • Owner decisions, not taken here:
    • registering /.well-known/wai-mark/ with IANA;
    • proposing the term to C2PA's soft-binding algorithm list;
    • anything addressed to the Code's task force or an EU working group;
    • running a resolver.

🤖 Generated with Claude Code

## What this is The second marking layer of `wai.media.synthesis`, made real. `extensions/synthesis.md` §7.4 left the watermark scheme as an empty slot (`wai-mark:<name>` + digest), so a detection was a signed statement and nothing more. This PR registers a scheme, `wai-mark:tent64`, and ties §7.3's agreement steps to it, adds the detection signpost of the Code of Practice's Measure 3.4(c)(ii), and measures what the mark survives. Texts read for it, fetched today: the Code of Practice on Transparency of AI-Generated Content, final text of 10 June 2026 (the Commission's document 129555, SHA-256 `7bd22c5a…bf9bc28`): Section 1 Measures 1.1, 2.1, 2.3, 3.2 to 3.4 and the Glossary's *signpost solution*; C2PA 2.4 §9.3, §18.10, §18.15.4.8, §18.15.5 (soft binding, `c2pa.watermarked.bound`); RFC 8615. ### The scheme (§7.4, §7.5) - **Frames and grid.** RGB8 frames of at least 128 × 128. Each frame is cut into a 64 × 64 grid by the edges ⌊i·W/64⌋ and ⌊j·H/64⌋, so the grid follows the picture through a rescale or a change of aspect. - **Pattern.** In each cell, a separable integer *tent* checkerboard ω. It is zero-sum, so a brightness change leaves a cell's statistic s = Σ ω·Y unchanged and a contrast change only scales it. - **Layout.** A public SHA-256 stream gives a shuffle of the 4096 cells, a sign for each cell, and a mask over the code bits. Each of the 248 code bits is carried by 16 cells scattered over the frame; 128 cells carry nothing. - **Code.** BCH(255, 71, 59) over GF(2^8), shortened to (248, 64). It corrects 29 errors and is decoded only within that radius. The generator polynomial is printed in hex and derived from its definition by both implementations. - **Embedding.** Informed and bounded: each bit gets the least amplitude a ≤ 12 at which 2·σ·T ≥ 3·Q, where T is the bit's statistic and Q its weight mass. The same delta goes to R, G and B, clamped. A bit the host already satisfies is left untouched. All integer arithmetic. - **Video.** The whole payload is repeated in every frame, and the detector sums the statistics over the frames. Reason: any single frame, a clip of any length, a clip with dropped or re-timed frames, or a still taken from the video carries the payload, and there is no position in time to recover. - **Detection.** Key-free. The detector tries the 8 quarter turns and mirrors in a fixed order. In each, it reads signs, unmasks, decodes, and accepts the first payload whose class byte is ≤ 3. ### What it guarantees (§7.5.6) - **No hidden channel.** The marked output is a pure function of the unmarked frames and the 8-byte payload: no key, no seed, no embedder choice. A verifier holding the unmarked frames recomputes the marked ones. If the digests are equal, the mark carries exactly the payload and nothing else, so no viewer or session identifier can ride on it. The text contrasts this with `forensic-watermark.md`, whose purpose is the opposite. - **False-positive bound.** Derived as 8 · 2^58 · V(248, 29) / 2^248 = **2^−61.39 (about 3.3·10^−19)**, the 8 being the orientations. It was checked on unmarked pictures: - the words read from the 32 measurement pictures in 8 orientations have ones with mean 124.33 and sd 7.85; - the 31,744 cross-picture pairs differ in a mean of 123.91 bits, sd 7.95, minimum 91; - the uniform model predicts 124 and 7.87 for both. - No unmarked picture under any attack gave a payload, and the photograph set agrees. - **PSNR floor.** The PSNR of full-amplitude embedding for the frame's size: 36.66 dB at 512², 38.73 dB at 256², 35.76 dB at 768 × 512. The absolute lower bound over all admitted sizes is 26.55 dB. ### Robustness, measured (§7.5.7, §7.5.8) The battery is 33 picture attacks and 7 clip attacks. Each is an integer function, with noise drawn from a fixed SHA-256 stream, so a run reproduces bit for bit. Python re-runs every picture attack on the corpus still and gets identical digests. The attacks are: - re-encoding with the crate's own `wai.video.int_motion` encoder at q = 4 to 48, and a staged base layer (half resolution, int_motion, then `wai-synthesis:upscale2`); - `denoise3` once and four times; - area resampling at 3/4, 1/2, 1/3, 1/4 and 3/2, plus `upscale2` and an aspect change; - crops of 1%, 2%, 5% and 10%; - mirror and a 90° turn; - integer bilinear rotations of 0.5°, 1° and 2°; - uniform noise of ±4 and ±16; - brightness ±32 and contrast ×1/2 and ×3/2; - a local blur over a quarter of the area; - for clips: inter coding, frame drop, one frame, scale and noise. The measurement sets are generated in-repo from fixed seeds: 16 spectral pictures (`wai.generate.spectral` with its committed model), 16 dead-leaves pictures, and 4 panning clips. The informative photographs column covers 24 lossless 768 × 512 photographs of a common test suite. They are measured with `wai_synthesis_mark_vectors measure`, named by SHA-256 in the corpus README and **not committed**. **Claims rule:** an attack is claimed only if every picture in every column was detected with its payload and no unmarked picture under it gave a payload. - **Claimed for pictures:** int-motion q4/8/16/32/48, staged-base-q8, denoise3 (×1 and ×4), scale 3/4, 1/2 and 3/2, upscale2, aspect-3/4, half-then-int-motion-q16, mirror, rotate-90, noise ±4/±16, brightness ±32, contrast 1/2 and 3/2, local-blur. - **Claimed for clips:** all clip attacks. - **Not claimed:** any crop (2% already fails everywhere), any small rotation (1° fails everywhere), scale 1/3 (fails), and scale 1/4 (1 of 24 photographs lost). - The spec says plainly that the detector does no shift or angle search, that key-free means anyone can find and remove the mark or forge a payload into other content, and that no adversarial resistance is claimed (§14). - **Imperceptibility:** mean PSNR 45.0 dB (spectral), 43.7 dB (leaves), 42.1 dB (clips), 43.6 dB (photographs); SSIM 0.97–0.98, clips 0.95. ### Verification (§7.3, §7.6, §7.8) - **Record changes.** A marking gains `embedding` (frame count and size, plus SHA-256 of the unmarked and marked frames). A detection gains `read` (frame count and size, plus SHA-256 of the frames read). - **Marking checks.** With the unmarked frames held, the verifier recomputes (`mark_input_mismatch`, `mark_recompute_mismatch`). With the decoded frames held, it compares (`samples_mismatch`). - **Detection checks.** With the frames held, the detector is run again (`detection_irreproducible`). This is where "an attack beyond the claimed robustness reported as detected-and-agreed" is refused. - **Agreement by inheritance.** A conversion that does not re-mark its output carries its input's mark, and the detection agrees with the conversion's claim through its inputs (`agree-inherited`). A capture claim over marked synthesized content is `layers_disagree`. - **New policy member.** `second_layer: require-proof` (`layer_not_proved`). - **Other new codes.** `scheme_mismatch` and `mark_evidence_invalid`. - **Sinks that generate (§7.8).** A sink that generates marks as part of the reconstruction its tier holds. A player that presents the unmarked frames gets `present_output_irreproducible`. - **`wai-mark:conformance` is kept.** It is the scheme with no algorithm. It exercises the records independent of any algorithm and stands for any scheme a verifier does not implement. ### Signpost and resolver (§7.7) - **In the content.** The openly disclosed mark itself. Its payload points at the claim, whose markings name any other schemes. - **In the metadata.** Each marking's C2PA statement: `c2pa.watermarked.bound` plus a `c2pa.soft-binding` assertion with `alg` = term, `alg-params` = digest and the payload as the block value. The marking's statement is part of §11.3's accepted output. - **Resolver.** A proposed `/.well-known/wai-mark/<name>` (the definition) and `/.well-known/wai-mark/<name>/payload/<p>` (claims by payload). ### Deliverables - **Spec:** `wai/extensions/synthesis.md` §7 rewritten (7.1–7.8), with matching updates to the header, §1, §11, §12 (6 new codes), §13 and §14. - **Reference:** - `wai-rs` `synthesis::mark`, under feature `synthesis` because the verifier needs it; - the `synthesis_mark` module (battery, measurement sets, PSNR/SSIM, corpus), under the new feature `synthesis_mark` = synthesis + video_int + generate + png_still, which adds no dependency; - the `wai_synthesis_mark_vectors gen|verify|measure` binary. - **Corpus:** `wai/live-conformance/synthesis-mark/`, with `scheme.json`, `vectors.json`, `battery.json` and `frames/<sha256>.rgb` (about 1.3 MB). It contains: - codewords, and decodes with 0, 1, 29, 30 and 40 bits flipped; - 3 embeddings, with per-bit amplitudes; - 8 detections; - a 66-entry battery sample; - 16 cases, including the negative controls the brief asked for: a mark with the wrong payload, a marked output that does not recompute (one extra bit, a hidden channel), a detection claiming a payload the content does not carry, and an attack beyond the claimed robustness that is not reported as agreeing. - **Synthesis corpus:** regenerated for the new members and statements, with 3 new refusal vectors. - **Second implementation:** - `live-conformance/_wai_mark.py` (scheme and battery, written from §7.5); - `synthesis_verify.py`, with the §7.6 checks; - `synthesis_mark_verify.py`, which checks the corpus, the spec's definition, generator, figures, table and claims, applies 14 mutations, and weakens its own verifier 3 ways. - **Housekeeping:** CI step in `wai-conformance.yml`; REGISTRY wire identifiers (`wai-mark:tent64` and `wai-mark:conformance` as schemes, `/.well-known/wai-mark/` as proposed); regenerated registry export; SPEC §5 paragraph; CHANGELOG; `live-conformance/README.md`; the corpus README with its Provenance line; the `int_kernels_vectors.rs` live-conformance reason extended. ### Checks All were run on the merged tree (origin/main at `aa0b98d33`, merged as `5d33f01b4`). Cargo commands ran on the build host through `rb`; Python and the scripts ran locally. | check | command | result | |---|---|---| | registry export | `cargo run --no-default-features --bin wai -- registry export --check` (after `registry export`) | up to date | | scheme and battery unit tests | `cargo test --no-default-features --features synthesis_mark --lib -- synthesis::mark synthesis_mark::` | 13 passed | | scheme corpus | `cargo test --release --no-default-features --features synthesis_mark --test synthesis_mark` | 5 passed (46 s): regenerates byte for byte, every vector and case, definition/generator/shuffle pinned, claims and figures checked against battery.json | | synthesis unit tests | `cargo test --no-default-features --features synthesis --lib synthesis::` | 17 passed | | synthesis corpus | `cargo test --no-default-features --features synthesis --test synthesis` | 4 passed | | registry | `cargo test --no-default-features --lib codecs::registry` | 14 passed (wire identifiers written in their sections) | | provenance and CLI | `cargo test --no-default-features --test carriage --test manifest_builders --test cli_wrap_intent --test cli_registry --test corpus_provenance` | all passed | | sha256 rule | `cargo test --release --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int --test int_kernels_vectors` | 8 passed | | clippy | `cargo clippy --no-default-features --features synthesis_mark --all-targets` | no notes in any file this PR touches; the remaining notes are pre-existing (jpegai_*) | | second implementation, scheme | `python3 -I live-conformance/synthesis_mark_verify.py live-conformance/synthesis-mark` | PASS: 14 mutations caught, 3 weakened verifiers caught | | second implementation, synthesis | `python3 -I live-conformance/synthesis_verify.py live-conformance/synthesis` | PASS, 41 vectors | | neighbouring verifiers | `live_rooms_verify.py rooms`, `live_events_verify.py events` | PASS | | repo scripts | `check-tests-in-ci.py`, `check-workflow-concurrency.sh`, `check-spec-refs.py`, `check-wai-section-refs.py`, `check-no-vanity-metrics.sh`, `test-public-text-checks.sh`, `check-comparative-prose.sh`, `check-orphan-sources.py` | all clean | | public names | `check-public-names.sh` with the private denylist | every file this PR touches is clean, as are this body and the commit messages; a full-tree run reports 9 lines in `wai-rs/src/live_provenance.rs`, which this PR does not touch (pre-existing) | | dependents and lockfiles | no new crate dependency; `synthesis_mark` composes existing features; no code outside wai-rs uses `synthesis` | nothing to rebuild; no Cargo.lock change | ### Falsifications Each was committed first, then run on the build host: the patch was applied, the guarding check run, and the file restored. | rule broken | check | exit | |---|---|---| | marking recomputation dropped (Rust) | `tests/synthesis_mark` every_vector_and_case | 101 | | detection re-run dropped (Rust) | same | 101 | | agreement by inheritance dropped (Rust) | same | 101 | | spec claims `crop-1%` | `the_extension_claims_only_what_the_measurement_shows` | 101 | | spec figure 124.33 changed to 124.34 | same | 101 | | reference amplitude 12 changed to 11 | `the_extension_pins_the_scheme` | 101 | | reference margin changed to 1/1, corpus not regenerated | `the_committed_corpus_regenerates_from_its_seeds` | 101 | | Python margin changed to 1/1 | `synthesis_mark_verify.py` | 1 | | Python decoder radius changed to 28 | same | 1 | | Python luma weights changed | same | 1 | | Python delta rounded half down | same | 1 | | Python spec claims `crop-1%` | same | 1 | | Python §7.6 recomputation skipped | same | 1 | Two attempts did not fail, and the reasons are worth recording: - My first "spec claims crop-1%" patch matched across a line break and was never applied. The harness now aborts when a patch does not apply, and both re-runs fail as shown above. - Flipping the tent's sign is a true symmetry, not a defect: ω is the product of two tents, so negating both leaves it unchanged. I replaced that falsification with the luma and rounding ones. ### Not done - **Audio** (the stretch goal) is not reached, and the spec says so (§1). Text has no scheme. - **No crop or rotation resynchronisation.** The scheme states these as not survived instead. - **Photograph licence.** The photographs are measured locally and not committed. The page hosting them gives no formal licence; it says the hoster understands them to be released for unrestricted usage. The informative column could be dropped if the owner prefers. - **Owner decisions, not taken here:** - registering `/.well-known/wai-mark/` with IANA; - proposing the term to C2PA's soft-binding algorithm list; - anything addressed to the Code's task force or an EU working group; - running a resolver. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A tent checkerboard in each cell of a 64 x 64 grid that follows the picture
through a rescale; a public stream shuffles the cells, signs them and masks the
code; the 8-byte payload is a BCH(255, 71) codeword shortened to 248 bits, each
bit carried by 16 cells; each bit is given the least amplitude up to 8 that
gives its statistic the sign the codeword asks with a margin of 3/2 of its
weight mass. The detector reads the signs in each of the 8 orientations and
decodes up to 29 errors.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A marking states the frames it embedded into (their size and the digests of the
unmarked and the marked frames) and a detection the frames it read. Holding the
unmarked frames, a verifier recomputes the marking's derivation; holding the
decoded frames, it runs the detector again. New codes: scheme_mismatch,
mark_evidence_invalid, mark_input_mismatch, mark_recompute_mismatch,
detection_irreproducible, layer_not_proved; a policy member second_layer; each
marking states its C2PA signpost. The synthesis corpus regenerates with the new
members and gives the codes that need no frames.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The battery applies the crate's own integer codecs (wai.video.int_motion's
encoder, a staged base through wai-synthesis:upscale2), the pinned derivations
of §4, area resampling, crops, quarter turns and mirrors, small rotations,
noise from a fixed stream, brightness, contrast and a local blur, each an
integer function of the frames. The measurement sets are generated from fixed
seeds: wai.generate.spectral's generator with its committed model, and a
dead-leaves generator. The corpus holds the scheme's definition, the code's
words, embeddings and detections over stored frames, a battery sample, cases
whose markings are recomputed and detections run again, and the measurement.
The amplitude cap is 12: at 8 the most textured photographs left bits short.
A conversion's detection agrees with the claim it inherits the mark from.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
§7.4 registers wai-mark:tent64 by its definition and digest and keeps
wai-mark:conformance as the scheme with no algorithm. §7.5 is the scheme: frames
and luma, the grid and the tent pattern, the stream, the shuffle, the signs and
the mask, the shortened BCH code, the informed embedding, the detector in eight
orientations, what it guarantees (no other channel, a PSNR floor, a
false-positive bound derived and checked), the battery and the table of what it
survives, with the claims it makes. §7.6 proves a marking by recomputation and
reproduces a detection; §7.7 gives the signpost in the content and in the C2PA
statement, and a well-known resolver; §7.8 has a sink that generates mark as part
of the reconstruction it is held to. _wai_mark.py is the scheme and the battery
in Python from the text; synthesis_verify.py runs §7.6's checks with it;
synthesis_mark_verify.py verifies the scheme's corpus, mutates it and weakens
itself in turn.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The wire identifiers wai-mark:tent64 and wai-mark:conformance (schemes, §7.4) and
/.well-known/wai-mark/ (proposed, §7.7); the registry export regenerated. A CI
step runs the scheme's unit tests, tests/synthesis_mark.rs in release and
synthesis_mark_verify.py. The corpus README states its provenance and names the
photographs of §7.5.8's informative column by SHA-256.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merge remote-tracking branch 'origin/main' into wai/synthesis-mark
All checks were successful
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 1m12s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m11s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 26s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 10m0s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Successful in 1m51s
wai-pack / wai-pack · linux (pull_request) Successful in 1m24s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 19m34s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 2m29s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 4m11s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 8m7s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 5m53s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 24s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 15s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 2m44s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 3m15s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 22m47s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m13s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 8m23s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 4m38s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 17s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 2m5s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 38m56s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 9m44s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 15m49s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 20s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 47s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 6m58s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Successful in 2m48s
wai-pack / wai-pack · macos (pull_request) Successful in 1m11s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 2m15s
5d33f01b4d
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merge origin/main into wai/synthesis-mark
Some checks failed
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Has been cancelled
WAI energy receipts / does this change touch it (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / does this change touch it (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
standards workspaces build + test / which workspaces this change touches (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (pull_request) Has been cancelled
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Has been cancelled
b0d1a3a492
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!407
No description provided.