WAI delivery bindings: secure-objects AAD in receipts (MOQT 22/18/16), CMSD read, steering named to CDNs, live C2PA signing #458

Closed
dcharlot wants to merge 16 commits from wai/delivery-bindings into main
Owner

The delivery-binding items of "WAI / Invisible Network — Global Energy & Cost Map" (2026-10-22): ingest #5 (MoQ secure-objects AAD), #6 (CMSD), the "Highlight" (energy-aware steering read by CDNs) and #7 (C2PA live per-segment signing). For the owner's review; not to be merged without it.

What each binding now says, and on what

#5 Secure-objects AAD in receipts (confidentiality §8.4, §11.6, §11.7)

  • Read: draft-ietf-moq-secure-objects-01 (6 July 2026, still current on datatracker 2026-10-22) §3.3.1, §3.5, §3.6, §3.7, §4.1–§4.3, §7.1 and Appendix B; draft-ietf-moq-transport-22 (1 October 2026, current) §2.1.1, §8.1, §8.3, §8.7, §10.4, §10.7, §11.2.1, §11.3.1, §16.8 and Appendix A.1–A.4; -18 §1.4.1, §1.4.3, §12.7; -16 §1.4, §1.4.2, §11.2; RFC 9000 §16; the MoQ WG milestones page.
  • The AAD was already aligned to -22 on main (Key ID at 0x3A4A, since -22 §16.8 gives 0x02 to OBJECT_DELIVERY_TIMEOUT). What was missing was "receipts must ride its object identity". Now:
    • §8.4: an object's identity is that AAD. An identity-bound object receipt (leaf_version 3) binds it, so a relay door or a subscriber refuses identity_mismatch when an object arrives with another priority, Key ID or immutable pair, with no key.
    • §11.6: -18 carries -22's bytes. -16 writes QUIC's integer, so a -16 endpoint or bridge re-encodes the Immutable Extensions pairs as -22 writes them before forming the AAD.
    • §11.7: the milestone "Request publication of Publication and Subscription Protocol to IESG" (due 2026-12-31), and what the next revision re-reads then.
  • Code:
    • joule-moq receipt (third leaf form, object_identity, identity_as_received, check_identity);
    • joule-moq-relay door;
    • joule-moq-interop wai-sealed;
    • live-conformance/e2ee/identity.json, from joule_moq_e2ee_vectors, checked by e2ee_verify.py.
  • Corrected: confidentiality §4.4, §8.1 and §8.2 said WAI_INNER_DIGEST and a receipt's content hash were one hash. They are not: plain BLAKE3 vs jwp-receipts §2.1's domain-separated hash. No corpus bytes change.

#6 CMSD (delivery-telemetry §6.6–§6.10)

  • Read: CTA-5006 (November 2022) §3, §4, §5, §6.1, §7 Table 1, §8.1 items 1–8, §8.2 and Annex A; RFC 9651 (CTA-5006 cites RFC 8941); RFC 9110 §5.3.
  • Emitting CMSD was already on main. This adds a reader that follows §8.1, refuses st=ll in the emitter (Table 1 has only v and l), and adds a read corpus differential-tested against the SVTA common-media-library.

Highlight: steering for CDNs (energy-steering §8.5)

  • The decision's digest goes in CMSD-Static on the steering response and in a CMCD custom key on media requests.
  • Each hop's signing key (the key of its WMH1 records, live-metering §3) goes in its own CMSD-Dynamic entry, with a "served by" check against the policy's pathway hops.
  • No CMSD or CMCD value is evidence, and WSP1/WSD1 keep their bytes.

#7 C2PA live (live-provenance §10.4, §11.7)

  • Both §19 methods (per-segment manifests, and the VSI emsg) were already on main since 2026-10-21.
  • Added: a LiveSigner that writes each segment as its last chunk closes and refuses what a verifier refuses, and a LiveVerifier that checks a stream as it arrives.
  • Cited: C2PA 2.4 §19.2.1, §19.2.2, §19.3.1, §19.3.2, §19.4.1, §19.4.3, §19.4.4.1, §19.6.2, §19.7.1–§19.7.3, §14.5.1 and §15.8.

Interop (build host, loopback only, 2026-10-22; no deployed endpoint reached)

  • Sealed objects with identity-bound receipts:
    • joule-moq-relay: every draft and every publisher/subscriber pair among 22, 18 and 16 passed, 30/30 objects identity-verified and opened.
    • The relay of a public MOQT relay network's open-source implementation, built from source (main 8bc03ed at -16, draft-18-dev 8779758 at -18): QUIC and WebTransport both passed 30/30. Group ID, Object ID, priority and Immutable Properties crossed unchanged.
    • Every --tamper identity run was refused with identity_mismatch (and the AEAD's bad_tag).
  • CMSD: @svta/cml-cmsd 1.1.1 agrees on every value the reader keeps. Where it differs is listed in the telemetry README: it applies none of §8.1's rules, decodes a Token as a string, and accepts Dates but not Display Strings.
  • C2PA: c2patool 0.28.2 and c2pa-rs 0.91.4.
    • Every good init segment is Trusted.
    • Every per-segment manifest validates against its own segment with assertion.bmffHash.match, including failover and key delivery.
    • Neither tool implements §19 segment validation (live-video assertion, segment-info map, session keys), so the VSI emsg is checked only by the reference and c2pa_live_verify.py. Details are in the c2pa-live README.

Falsified once each

  • receipt check_identity passing anything, the identity-bound leaf dropping the identity, the as-received identity ignoring Immutable Properties;
  • e2ee_verify.py without the identity in the leaf, or without the -16 re-encoding;
  • the CMSD reader and differential (6 cases);
  • steering §8.5 in Rust and in Python (9 cases);
  • the live signer and incremental verifier (4 cases).

Every one failed with the expected reason and was restored. Of the existing corpus files, only telemetry-conformance/expected/cmsd.json changes, in the one wai-op value decided below; the rest of the changes are READMEs and verifiers, plus 33 new vector files. Every package and receipt keeps its bytes.

Blocked or not done

  • SVTA Open Caching (SVTA2048, SVTA2049, SVTA2050-1) could not be downloaded (HTTP 405 to non-browser posts), so it is not bound. energy-steering §1 says so.
  • CTA-5006 is sold to non-members and its free link is dead. It was read from the Wayback Machine's copy of CTA's own PDF, and the corpus models Annex A rather than copying it.
  • The "legacy systems due 2 December 2026" date appears only in secondary sources about the Digital Omnibus. EUR-Lex returned empty pages, so no application date is stated.
  • secure-objects has no newer revision and no IESG milestone; the Key ID stays at 0x3A4A until it and MOQT's registry agree.
  • jwp-receipts §2.2 does not list the identity-bound leaf. It is defined in confidentiality §8.4, beside the AAD, so the per-hop receipt work on wai/energy-truth (which owns jwp-receipts, live-metering and energy-measurement) can reference it rather than restate it.
  • The browser room member (joule-moq-web) does not yet sign identity-bound receipts.
  • No workflow was changed. Every new test sits in a file CI already runs.

science.transaction.wai-op is a String (decided)

CTA-5006 §4 item 1: custom keys "MUST only utilize the String value type". delivery-telemetry §6.2 now writes the operation token as a String, and §6.7 reads only a String. A Token for the key is a value not of its type, so it is ignored and stated (type, §6.8; CTA-5006 §8.1 item 4).

  • The emitter, the reader and the CML differential follow.
  • The one value in expected/cmsd.json that held it changes, from science.transaction.wai-op=crop to science.transaction.wai-op="crop". It is the only existing corpus byte this PR changes.
  • cmsd-read/wai-keys now writes the String.
  • New vector cmsd-read/wai-op-a-token: the Token is ignored, the rest read.
  • The §6.9 note on the former form is removed.

Overlap with other open PRs

There is no overlap with #426 or #438. wai/CHANGELOG.md, wai/SPEC.md (three short additions in §5) and delivery-telemetry.md §6 are also edited by #429, #432, #433 and #435; whichever merges second resolves.

The site's status and references rows are on branch wai/delivery-bindings of the WAI site repository: a Building item for this PR, plus stale rows corrected to what main holds (live provenance, CMSD emission, MOQT drafts 22/18/16). It is built and e2e-checked on the build host, and not deployed.

🤖 Generated with Claude Code

The delivery-binding items of "WAI / Invisible Network — Global Energy & Cost Map" (2026-10-22): ingest #5 (MoQ secure-objects AAD), #6 (CMSD), the "Highlight" (energy-aware steering read by CDNs) and #7 (C2PA live per-segment signing). For the owner's review; not to be merged without it. ## What each binding now says, and on what **#5 Secure-objects AAD in receipts** ([confidentiality](wai/extensions/confidentiality.md) §8.4, §11.6, §11.7) - Read: `draft-ietf-moq-secure-objects-01` (6 July 2026, still current on datatracker 2026-10-22) §3.3.1, §3.5, §3.6, §3.7, §4.1–§4.3, §7.1 and Appendix B; `draft-ietf-moq-transport-22` (1 October 2026, current) §2.1.1, §8.1, §8.3, §8.7, §10.4, §10.7, §11.2.1, §11.3.1, §16.8 and Appendix A.1–A.4; `-18` §1.4.1, §1.4.3, §12.7; `-16` §1.4, §1.4.2, §11.2; RFC 9000 §16; the MoQ WG milestones page. - The AAD was already aligned to -22 on main (Key ID at `0x3A4A`, since -22 §16.8 gives `0x02` to OBJECT_DELIVERY_TIMEOUT). What was missing was "receipts must ride its object identity". Now: - §8.4: an object's identity is that AAD. An identity-bound object receipt (`leaf_version` 3) binds it, so a relay door or a subscriber refuses `identity_mismatch` when an object arrives with another priority, Key ID or immutable pair, with no key. - §11.6: -18 carries -22's bytes. -16 writes QUIC's integer, so a -16 endpoint or bridge re-encodes the Immutable Extensions pairs as -22 writes them before forming the AAD. - §11.7: the milestone "Request publication of Publication and Subscription Protocol to IESG" (due 2026-12-31), and what the next revision re-reads then. - Code: - `joule-moq` receipt (third leaf form, `object_identity`, `identity_as_received`, `check_identity`); - `joule-moq-relay` door; - `joule-moq-interop` `wai-sealed`; - `live-conformance/e2ee/identity.json`, from `joule_moq_e2ee_vectors`, checked by `e2ee_verify.py`. - Corrected: confidentiality §4.4, §8.1 and §8.2 said `WAI_INNER_DIGEST` and a receipt's content hash were one hash. They are not: plain BLAKE3 vs jwp-receipts §2.1's domain-separated hash. No corpus bytes change. **#6 CMSD** ([delivery-telemetry](wai/extensions/delivery-telemetry.md) §6.6–§6.10) - Read: CTA-5006 (November 2022) §3, §4, §5, §6.1, §7 Table 1, §8.1 items 1–8, §8.2 and Annex A; RFC 9651 (CTA-5006 cites RFC 8941); RFC 9110 §5.3. - Emitting CMSD was already on main. This adds a reader that follows §8.1, refuses `st=ll` in the emitter (Table 1 has only `v` and `l`), and adds a read corpus differential-tested against the SVTA common-media-library. **Highlight: steering for CDNs** ([energy-steering](wai/extensions/energy-steering.md) §8.5) - The decision's digest goes in CMSD-Static on the steering response and in a CMCD custom key on media requests. - Each hop's signing key (the key of its `WMH1` records, live-metering §3) goes in its own CMSD-Dynamic entry, with a "served by" check against the policy's pathway hops. - No CMSD or CMCD value is evidence, and `WSP1`/`WSD1` keep their bytes. **#7 C2PA live** ([live-provenance](wai/extensions/live-provenance.md) §10.4, §11.7) - Both §19 methods (per-segment manifests, and the VSI `emsg`) were already on main since 2026-10-21. - Added: a `LiveSigner` that writes each segment as its last chunk closes and refuses what a verifier refuses, and a `LiveVerifier` that checks a stream as it arrives. - Cited: C2PA 2.4 §19.2.1, §19.2.2, §19.3.1, §19.3.2, §19.4.1, §19.4.3, §19.4.4.1, §19.6.2, §19.7.1–§19.7.3, §14.5.1 and §15.8. ## Interop (build host, loopback only, 2026-10-22; no deployed endpoint reached) - **Sealed objects with identity-bound receipts:** - `joule-moq-relay`: every draft and every publisher/subscriber pair among 22, 18 and 16 passed, 30/30 objects identity-verified and opened. - The relay of a public MOQT relay network's open-source implementation, built from source (`main` 8bc03ed at -16, `draft-18-dev` 8779758 at -18): QUIC and WebTransport both passed 30/30. Group ID, Object ID, priority and Immutable Properties crossed unchanged. - Every `--tamper identity` run was refused with `identity_mismatch` (and the AEAD's `bad_tag`). - **CMSD:** `@svta/cml-cmsd` 1.1.1 agrees on every value the reader keeps. Where it differs is listed in the telemetry README: it applies none of §8.1's rules, decodes a Token as a string, and accepts Dates but not Display Strings. - **C2PA:** c2patool 0.28.2 and c2pa-rs 0.91.4. - Every good init segment is `Trusted`. - Every per-segment manifest validates against its own segment with `assertion.bmffHash.match`, including failover and key delivery. - Neither tool implements §19 segment validation (live-video assertion, segment-info map, session keys), so the VSI `emsg` is checked only by the reference and `c2pa_live_verify.py`. Details are in the c2pa-live README. ## Falsified once each - receipt `check_identity` passing anything, the identity-bound leaf dropping the identity, the as-received identity ignoring Immutable Properties; - `e2ee_verify.py` without the identity in the leaf, or without the -16 re-encoding; - the CMSD reader and differential (6 cases); - steering §8.5 in Rust and in Python (9 cases); - the live signer and incremental verifier (4 cases). Every one failed with the expected reason and was restored. Of the existing corpus files, only `telemetry-conformance/expected/cmsd.json` changes, in the one `wai-op` value decided below; the rest of the changes are READMEs and verifiers, plus 33 new vector files. Every package and receipt keeps its bytes. ## Blocked or not done - SVTA Open Caching (SVTA2048, SVTA2049, SVTA2050-1) could not be downloaded (HTTP 405 to non-browser posts), so it is not bound. energy-steering §1 says so. - CTA-5006 is sold to non-members and its free link is dead. It was read from the Wayback Machine's copy of CTA's own PDF, and the corpus models Annex A rather than copying it. - The "legacy systems due 2 December 2026" date appears only in secondary sources about the Digital Omnibus. EUR-Lex returned empty pages, so no application date is stated. - secure-objects has no newer revision and no IESG milestone; the Key ID stays at `0x3A4A` until it and MOQT's registry agree. - jwp-receipts §2.2 does not list the identity-bound leaf. It is defined in confidentiality §8.4, beside the AAD, so the per-hop receipt work on `wai/energy-truth` (which owns jwp-receipts, live-metering and energy-measurement) can reference it rather than restate it. - The browser room member (`joule-moq-web`) does not yet sign identity-bound receipts. - No workflow was changed. Every new test sits in a file CI already runs. ## `science.transaction.wai-op` is a String (decided) CTA-5006 §4 item 1: custom keys "MUST only utilize the String value type". delivery-telemetry §6.2 now writes the operation token as a String, and §6.7 reads only a String. A Token for the key is a value not of its type, so it is ignored and stated (`type`, §6.8; CTA-5006 §8.1 item 4). - The emitter, the reader and the CML differential follow. - The one value in `expected/cmsd.json` that held it changes, from `science.transaction.wai-op=crop` to `science.transaction.wai-op="crop"`. It is the only existing corpus byte this PR changes. - `cmsd-read/wai-keys` now writes the String. - New vector `cmsd-read/wai-op-a-token`: the Token is ignored, the rest read. - The §6.9 note on the former form is removed. ## Overlap with other open PRs There is no overlap with #426 or #438. `wai/CHANGELOG.md`, `wai/SPEC.md` (three short additions in §5) and `delivery-telemetry.md` §6 are also edited by #429, #432, #433 and #435; whichever merges second resolves. The site's status and references rows are on branch `wai/delivery-bindings` of the WAI site repository: a Building item for this PR, plus stale rows corrected to what main holds (live provenance, CMSD emission, MOQT drafts 22/18/16). It is built and e2e-checked on the build host, and not deployed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A third leaf form, the identity-bound one of WAI confidentiality §8.4 (leaf_version 3):
the labelled leaf's fields under the domain moq-jwp:merkle-leaf\x03, then the object's
identity, the SECURE_OBJECT_AAD of draft-ietf-moq-secure-objects-01 §3.5 as MOQT -22
encodes it: Group ID (8 bytes), Object ID (4), Publisher Priority (1) and the value of
its Immutable Properties pair. The receipt carries the identity (u32 length, then the
bytes, after the class), so a hop checks what it forwards in the clear against what the
publisher signed, with no key: the priority, the Key ID and every other immutable pair.

- receipt: object_identity, identity_as_received (one Immutable Properties pair or none;
  an object read from -16 through the bridge has -22's encoding), check_identity with
  the part that differs (identity_difference: group_object, publisher_priority,
  immutable_properties, length), ReceiptBuilder::add_identity_object and identity_for;
  the label requires a class beside an identity and an identity of the receipt's own
  Group ID and Object ID; the binary form reads leaf_version 3 and refuses an identity
  shorter than 13 bytes or longer than 13 + 2^16 - 1.
- joule-moq-relay: the door forms each object's identity as it arrived (the header's
  priority, or the track's DEFAULT_PUBLISHER_PRIORITY it inherits, as fan-out uses) and
  refuses identity_mismatch; the sealed-objects test's receipts are identity-bound and
  each subscriber checks them, and a new test refuses at the door a sealed object whose
  Immutable Properties changed after its receipt.

Every receipt in the legacy and labelled forms keeps its bytes: the new form is opt-in,
and ObjectReceipt's new field is absent from the JSON and binary forms when unset.
Falsified: check_identity passing anything fails the unit test and the door test (group
1 delivered); a leaf that drops the identity fails the unit test; an identity that
ignores Immutable Properties fails the as-received unit test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The wai-receipts scenario with each media object sealed as an MoQ secure object
(draft-ietf-moq-secure-objects-01 on MOQT -22's objects, as WAI confidentiality §4 places
it: suite 0x0004, the Key ID inside Immutable Properties, no receipt property) and each
object receipt bound to its identity (§8.4). The subscriber forms each object's identity
from what arrived, checks it against its receipt, and opens the object under that same
AAD. It shows whether a relay carries the bytes secure objects authenticates unchanged.
`--tamper identity` sends one object with one more pair inside its Immutable Properties
than its seal and receipt bound; scripts/local.sh runs wai-sealed at each draft and
across drafts, and requires that tamper to fail.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
joule_moq_e2ee_vectors gen now also writes live-conformance/e2ee/identity.json (WAI
confidentiality §8.4, §11.6), from the corpus's seeds: a sealed video track's group whose
object receipts bind each object's identity, in the WJR1 form a receipts track carries,
with each sealed payload, content hash, identity and leaf; four copies with one defect
each (an identity of another object: identity_malformed; an identity dropped and a
priority restated inside one: unverified; an identity length below 13: malformed); and
thirteen arrivals of those objects at a hop, at -22, -18 and -16, each checked against its
receipt: pass, or identity_mismatch naming group_object, publisher_priority or
immutable_properties. The -16 arrivals state the pairs as -16 carries them and as -22
encodes them, and one that takes -16's bytes for -22's fails, which is why §11.6 has a
-16 hop re-encode them. vectors.json regenerates byte for byte; verify checks both files.

e2ee_verify.py, the second implementation, reads the bundle in jwp-receipts §2.7's binary
form with the third leaf form, checks the group's signature under the publisher's key
(strict Ed25519), rebuilds each leaf from the sealed payload's content hash and the
identity and walks it to the signed root, re-encodes -16's pairs (QUIC's integer) as -22
encodes them, and names the part that differs; seven mutated copies of identity.json
must each fail. Falsified: a v3 leaf without the identity fails every group; skipping
the -16 re-encoding fails draft16-converted.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- §8.4: an object's identity is the AAD secure objects §3.5 forms for it, as MOQT -22
  encodes its fields; an identity-bound object receipt (leaf_version 3) binds it beside the
  labelled leaf's fields; whoever holds the object and its receipt forms the identity as
  the object arrived and refuses identity_mismatch, naming group_object,
  publisher_priority or immutable_properties; identity_malformed for a receipt whose
  identity is not its own. A publisher of a sealed track SHOULD sign in this form (§8.1).
  The reasons are §8.4's own, so §13 and vectors.json keep their codes.
- §11.6: -18 carries -22's integers and pairs and keeps Immutable Properties' bytes
  (-18 §1.4.1, §1.4.3, §12.7); -16 writes QUIC's integer (-16 §1.4, RFC 9000 §16), so a
  -16 endpoint or bridge re-encodes the pairs of Immutable Extensions (-16 §11.2) as -22
  writes them before it forms the AAD; a value past 2^62 - 1 does not cross -16.
- §11.7: the working group's milestone "Request publication of Publication and
  Subscription Protocol to IESG", due 31 December 2026, and what the next revision of this
  text re-reads when it is made; the Key ID stays at 0x3A4A until secure objects and the
  registry agree.
- §4.4, §8.1, §8.2 corrected: WAI_INNER_DIGEST is BLAKE3 over the payload alone, and a
  receipt's content hash is jwp-receipts §2.1's (domain moq-jwp:object\x01); the text said
  they were the same hash. The corpus's bytes are unchanged; its README says which is which.
- Texts read: MOQT -18 and -16 at the sections named, RFC 9000 §16, the MoQ working
  group's milestones as listed on 22 October 2026.
- joule-moq and joule-moq-relay READMEs: the identity-bound receipt, the door's check, the
  -16 re-encoding row, and the runs of 2026-10-22 through joule-moq-relay at every draft
  pair and through another implementation's open-source relay built from source at -16
  and at -18, on loopback: every sealed object's identity matched its receipt and every
  object opened; every tamper was refused.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The whole-grammar Structured Field parser energy steering wrote for a client's CMCD
(Dates, Display Strings, Byte Sequences checked as base64, parameters on members) moves
to telemetry::sf::full, with a List parser beside its Dictionary parser and the RFC 9110
§5.3 joining of field lines. energy_steering re-exports it as `sf`, so its callers and
tests are unchanged; the CMSD reader of delivery-telemetry §6.6 uses the same parser.
The subset parser the reports' own round trips use stays as it was.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
CMSD was written (§6.1–§6.5) and never read. The reader (telemetry::cmsd::read) takes a
response's CMSD-Static and CMSD-Dynamic field lines, joins each field's lines (RFC 9110
§5.3) and parses it whole (RFC 9651 §4.2), and then, as CTA-5006 (November 2022) §8.1
has it: a field that does not parse is ignored whole (item 1); a `v` above 1 ignores the
data set (item 8); a value not of its key's Table 1 type is ignored alone (item 4); a key
the header does not reserve and that is not a custom key is ignored (item 3), `tl` among
them; custom keys are read only as Strings (§4 item 1). CMSD-Dynamic entries keep their
order, origin first (§4). Everything ignored is stated with its field, entry, key and why.

Table 1's full key set is read, `n`, `br` and `v` included; `sf` as a Token or an Inner
List of Tokens, since Table 1 and Annex A differ; `nor` and `nrr` cut at `|`, a range
element empty or start-end. The emitter now refuses `st=ll`, which Table 1 does not have
(no corpus byte changes: no CMSD case used it). §6.9 states where CTA-5006 is read one
way, among them that §6.2 writes `wai-op` as a Token, which §4 item 1 does not allow for a
CMSD custom key; the corpus keeps §6.2's bytes and the reader takes a Token or a String.

The corpus gains cmsd-read/ (29 cases, nine in the shape of CTA-5006 Annex A's examples,
written with the corpus's own values, since CTA-5006 carries no reproduction licence) and
expected/cmsd-read.json; `wai_telemetry verify` reads every case and requires each field
the crate writes to read back as the description it came from. Every existing file of
telemetry-conformance keeps its bytes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
cml/differential.mjs now decodes every cmsd-read/ case with the library's CMSD decoders
(@svta/cml-cmsd 1.1.1 over @svta/cml-structured-field-values 1.1.6, the locked versions)
and requires: the library's value for every key the reader keeps; the library's failure
exactly where the reader ignores a field whole; and, for every key the reader ignores for
its type or calls unknown, that CTA-5006 §7 Table 1 and §4 item 1, written here in
JavaScript and applied to the library's own Structured Field decoding, agree. Each
remaining difference must be of a named kind and is counted: the library applies none of
§8.1's reading rules (type, unknown, version); its decodeCmsdStatic returns a Token as a
string, so `n=origin` and `n="origin"` decode alike; and its parser takes RFC 9651 Dates
but not Display Strings. The README states each kind.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Steering already decides from the hops' signed per-hop records (live-metering §3) and
writes the steering manifest players read; nothing a CDN parses named the decision or the
hops. §8.5 binds them to the fields CDNs and players already parse:

- a steering response SHOULD carry CMSD-Static `n`, `ot=m` and the custom String key
  science.transaction.wai-sd, the decision's digest (CTA-5006 §3, §4 item 1, §7 Table 1);
  a client checks it against the manifest's member (§8.4): bound, absent, or
  cmsd_decision_mismatch, keeping to the manifest either way;
- a client applying a manifest SHOULD add science.transaction.wai-sd to its media
  requests' CMCD (CMCD-Session shard or the CMCD query), inserted in bytewise order with
  every other member unchanged, so a CDN's CMCD logs name the decision behind each
  request; the observation schema and the telemetry corpus are unchanged;
- a hop SHOULD add its own CMSD-Dynamic entry with science.transaction.wai-hop, the key
  that signs its per-hop records (CTA-5006 §4 item 5, §5); it reads nothing else of the
  records, so it holds for any later per-hop record or receipt signed by that key;
- "served by": a client's or auditor's check of which policy hops a response's
  CMSD-Dynamic names on the pathway it applied, with pathway_unknown, served_malformed,
  served_unknown_hop, served_off_pathway and served_out_of_order.

The decision rule reads no CMSD value and no CMCD key but §4.4's (CTA-5006 §8.1 item 7,
§9.2); WSP1, WSD1 and vectors.json keep their bytes. §8.5 has its own reasons table, so
§10 and Reason::ALL are unchanged. SVTA Open Caching is named out of scope: its texts
were not read for this revision.

Code: energy_steering::cdn. Corpus: live-conformance/steering/cdn.json, written by
`wai_energy_steering_vectors gen` from vectors.json and checked by `verify` and
tests/energy_steering.rs (regenerates byte for byte; every case its outcome; §8.5 lists
exactly cdn::Reason::ALL; every reason refuses a case).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The second implementation reads cdn.json beside vectors.json: it writes the steering
response's CMSD-Static field and the hops' CMSD-Dynamic entries, checks each response
against its manifest, adds the decision's CMCD key, and attributes each response, with a
List parser added to its own RFC 9651 parser and the CMSD reading §8.5 needs (field lines
joined, a later version ignoring the data set, custom keys as Strings). It requires every
case's outcome, reason and entry, that §8.5's table lists exactly the reasons it raises,
and that each of fourteen mutated copies of cdn.json fails.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
verify() becomes LiveVerifier given a whole stream at once. LiveVerifier
starts on the initialization segment (§11.3), takes each segment in the
order received once the receipts of its group and of the groups before it
are held (§11.4), and checks the end (§11.5). What it holds does not grow
with the stream: the running count of the receipts' objects replaces the
walk from firstGroup at every segment, receipts of groups passed are let
go, and §11.5 keeps the first group a segment passed over. After a refusal
every call gives that refusal again.

Every vector of live-conformance/c2pa-live keeps its outcome through the
wrapper: tests/live_provenance.rs is unchanged and passes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
LiveSigner writes the initialization segment, then a track's segment from
its chunks as they travelled and its group receipt, or a take's from its
chunks and the device's last commitment, under the per-segment C2PA
Manifest Box method (C2PA 2.4 §19.3), the Verifiable Segment Info method
in emsg (§19.4), or both, delivering later session keys in a segment's
manifest (§19.4.3). Before it writes a segment it checks what a verifier
will (§11.4 steps 1, 2, 4, 5, 6) and refuses with the verifier's Reason;
a refusal changes nothing it holds. LiveState is what it holds between
segments, all of it public, and resume() continues a stream from it.

The corpus checks, in tests/live_provenance.rs and
wai_live_provenance_vectors verify: the live signer writes every good
stream byte for byte one segment at a time, the failover's second
packager resumed from the first's state at its third segment; it refuses
one defect of each kind with its reason, keeps its state, and the stream
then continues as the corpus's; and every vector, verified segment by
segment as it would arrive, gets the outcome the whole stream gets. No
corpus file changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
§10.4: a packager signs a segment once its last chunk closes (the hard
binding covers every chunk, C2PA 2.4 §19.2.1, §19.2.2); what it holds
between segments, all public but its keys, so another packager continues
from it (§10.3); what it checks before it releases a segment, each defect
with §12's reason; session keys delivered ahead of their first segment
(§19.4.3, §19.4.4.1, §19.6.2). A player that presents parts of a segment
before its C2PA boxes presents them unverified: this revision signs
whole segments.

§11.7: a verifier checks each segment once the receipts of its group and
of the groups before it are held, holds what does not grow with the
stream, and stops at the first refusal. §13 names the new checks;
capture-binding §11 names the live signer.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The corpus read by the C2PA command-line tool 0.28.2 (built on the C2PA
Rust library 0.91.2) and by the library 0.91.4 through an uncommitted
harness, each under the corpus's anchor:

- given an anchor and no EKU list, the library clears its accepted EKUs
  and keeps e-mail, time-stamping and OCSP by name: every claim-signing
  certificate is signingCredential.invalid and the time-stamping one
  Trusted; with its own default list as trust_config, the good
  initialization segments are Trusted, as with 0.27.22;
- each per-segment manifest of every good stream, read against its own
  segment, is Trusted with assertion.bmffHash.match, and the manifest
  negatives get the §15 codes §12 gives them;
- neither implements §19's validation (no source names the live video
  segment assertion, the segment-info scheme or session keys); their
  fragment path is Appendix A.5.4's Merkle form, which refuses every
  stream here; a segment alone is not read.

live-provenance §13 and its header name implementations, plural.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai SPEC §5, CHANGELOG, live-conformance README: the delivery bindings
Some checks failed
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m51s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Failing after 2m25s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 1m55s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 19s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 4m27s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 10s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 27s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m13s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 32s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 2m59s
wai-pack / wai-pack · linux (pull_request) Successful in 1m44s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (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 energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
wai-pack / wai-pack · macos (pull_request) Has been cancelled
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been cancelled
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been cancelled
wai-pack / wai-pack · dependency licences (pull_request) Has been cancelled
wai-quantum studio and desktop / studio · browser (pull_request) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (pull_request) Has been cancelled
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
wai-pack / wai-pack · linux-arm64 (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
815c0e0d4f
SPEC §5: wai.live.e2ee's receipts may bind each object's identity, the AAD it is sealed
under, formed as MOQT -22 encodes it on any path; wai.live.steering's decision and hops
named in CMSD and CMCD for CDNs, none of it evidence; wai.live.provenance signed and
verified segment by segment as the stream runs. CHANGELOG v0.4.0: one entry for the
bindings. live-conformance README: joule_moq_e2ee_vectors also writes identity.json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
delivery-telemetry §6.2, §6.7: science.transaction.wai-op is a String, as CTA-5006 §4 item 1 has a custom key
Some checks failed
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 3m0s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 11m28s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 1m40s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 29s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 5m17s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Failing after 4m5s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 13s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 29s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m19s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 32s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m57s
standards workspaces build + test / macOS energy backends (pull_request) Failing after 6m16s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 11m12s
wai-pack / wai-pack · linux (pull_request) Successful in 1m33s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 20m34s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 15m39s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 4m1s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Failing after 22m54s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m54s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 1m29s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 7m21s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 2m17s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 7m9s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 23m48s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 29s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m34s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 7m47s
wai-pack / wai-pack · macos (pull_request) Successful in 1m21s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 2m33s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h2m11s
aca6c347b8
CTA-5006 (November 2022) §4, item 1: custom keys "MUST only utilize the String value
type". §6.2 wrote the operation token as a Token; it now writes a String holding the
token, and §6.7 reads only that form. A Token for the key is a value not of its type, so
a reader ignores it and states so (`type`, §6.8; CTA-5006 §8.1 item 4). The §6.9 note
that kept the former form is removed.

- The emitter (CmsdStatic::encode) writes the String; the reader takes a String holding a
  token and ignores a Token; the CML differential encodes a String and its Table-1 check
  no longer takes a Token for the key; the unit test's expected field follows.
- telemetry-conformance: the one value of expected/cmsd.json that held the key changes,
  `science.transaction.wai-op=crop` to `science.transaction.wai-op="crop"`;
  cmsd-read/wai-keys writes the String; new cmsd-read/wai-op-a-token: the Token ignored,
  `type`, and `ot` read beside it.
- CHANGELOG notes it.

Falsified: a reader that takes the Token again fails wai_telemetry verify on
wai-op-a-token; an emitter that writes the Token again fails verify on expected/cmsd.json
and the unit test; a differential whose check takes the Token fails on wai-op-a-token.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
dcharlot closed this pull request 2026-11-06 03:33:51 -05:00
Some checks failed
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 8s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 8s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 6s
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Successful in 1m52s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
WAI conformance / does this change touch it (pull_request) Successful in 6s
WAI energy receipts / does this change touch it (pull_request) Successful in 8s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 10s
joule-code conformance gate / joule-moq-relay, every target (pull_request) Successful in 2m30s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 7s
wai-web wasm32 / does this change touch it (pull_request) Successful in 7s
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 3m26s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Failing after 3m36s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 3m0s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 11m28s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 1m40s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 29s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 5m17s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Failing after 4m5s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 13s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 29s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m19s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 32s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m57s
standards workspaces build + test / macOS energy backends (pull_request) Failing after 6m16s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 11m12s
wai-pack / wai-pack · linux (pull_request) Successful in 1m33s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 20m34s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 15m39s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 4m1s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Failing after 22m54s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m54s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 1m29s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 7m21s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 2m17s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 7m9s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 23m48s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 29s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m34s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 7m47s
wai-pack / wai-pack · macos (pull_request) Successful in 1m21s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 2m33s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h2m11s

Pull request closed

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!458
No description provided.