open-standards/wai/live-conformance/presentation/README.md
2026-10-21 12:28:47 -04:00

90 lines
6 KiB
Markdown

# live-conformance/presentation — ad presentation
The corpus of [`extensions/ad-presentation.md`](../../extensions/ad-presentation.md) §12: rooms
of [live-ads](../../extensions/live-ads.md) breaks under ad policies of version 2 that require
presentation claims; each viewer's break-form `wai.video.ssai` receipt, its session derived
from the viewer's session key; the `WPC1` claims the viewers' players signed, good ones and a
control of each check; the creatives and the attestation issuer a verifier holds; what a
verifier states of them; and each accepted room's HLS tags, byte for byte.
**Presentation equivalence.** For each vector, a conforming verifier given its `descriptor`,
`grants`, `key_log`, `chains`, `policy`, `pt0_ms`, `receipts`, `pods` and `claims`, and the
corpus's `creatives` and `issuers` (§9.1), reaches the outcome the vector states, with
everything live-ads §9.4 states (see [`../ads/README.md`](../ads/README.md)) and, for an
accepted room under a policy that requires claims (§9.4):
- `receipt_facts`, each receipt's `presented_spend_micros` beside its `spend_micros`, and its
`impressions`, each `slot`, the index of its own `claim` among the claims or `null`, and its
`verdict`: `presented` or the reason it does not count;
- `claim_facts`, each claim in the order given: its `digest` (`null` when it does not read),
its `verdict`, the `receipt` and `slot` it names (from §9.2 step 3 on), and, for an
impression's own claim, the `quartile` it reaches, its `run_ms` and its `viewable_ms`;
- `spend_micros`, the presented spend of every receipt together.
`claim_about` says what each claim is, and is not checked. A verifier that reaches another
outcome, reason, place, verdict, fact, spend or tag byte does not conform.
The corpus also carries `rfc9578_a2_vector1`: the `pkI`, TokenChallenge and token of RFC 9578
Appendix A.2's first test vector, as printed there. A conforming verifier verifies that token under
that key before it reads any token of the corpus.
**Provenance.** Generated here: `wai_ad_presentation_vectors gen` writes every file from rooms
authored in code, from fixed seeds, as `../ads/` is written. Each session key is SHA-256 of
`wai:ad-presentation/corpus/player`, a zero byte and its number (`players` lists their public
keys); each media object's content hash SHA-256 of a fixed label and two numbers; each token's
nonce SHA-256, and its salt SHA-384, of a fixed label and its number. The issuer's key is the
test key RFC 9578 Appendix A.2 publishes; the generator signs the tokens with it. Ed25519 and
RSASSA-PSS over a given salt are deterministic, so every file is the same bytes on every
machine. The generator asserts that RFC 9578's token verifies, that each vector reaches the
outcome and each claim the verdict it was written to have, and that the corpus gives every
reason of §11, before it writes. No recorded data.
## Layout
- `vectors.json`: `rfc9578_a2_vector1`; `creatives`, each its `objects` (`content_hash`,
`duration_ms`); `issuers`, each its `name`, `token_type` and `keys` (DER
SubjectPublicKeyInfo, hexadecimal); `players` (informative); and `vectors`.
- `NAME.m3u8`: the HLS tags of each accepted room.
The rooms, each built as `../ads/`'s are (a producer, a viewer and the decider, whose grant
carries the ad budget):
- `video_rule`: a 30 s mid-roll under the video rule, five viewers each filled with ad-x
(15 s) and ad-y (15 s). Presented: viewer 1's two (one partly out of view, with 3 s at 60%),
viewer 3's ad-x, and viewer 7's ad-y measured over the player's area. Controls:
`claim_not_viewable` (40% of the pixels; two runs of 1.5 s broken by a pause; a television
assumption the video rule does not admit), `claim_duplicate`, `claim_key_unbound`,
`claim_asset_mismatch`, `claim_quartile_mismatch`, `bad_signature` (its impression then
`claim_absent`), `claim_malformed` (a signed claim that skips with no resumption stated, and
a truncated claim), and `claim_no_decision`.
- `cross_media_attested`: an out and its in under the cross-media rule, an audible run and an
attestation of type 0x0002 from `issuer.example`. Presented: viewer 11's ad-x and viewer 14's
two. Controls: `attestation_absent`, `attestation_invalid` (another impression's token; an
altered authenticator) and `claim_not_viewable` (muted; 90% of the pixels).
- `ceiling_counts_presented`: a budget of 1 minor unit and two receipts each billing 600 000
millionths; one has a claim, so the presented spend is within the ceiling: accepted.
- `ceiling_exceeded_by_presented`: the same, both with claims: `funds_exceeded`.
- `policy_attestation_without_claim`: `ad_policy_malformed`.
## Running
```
cd ../../wai-rs
cargo run --no-default-features --features live_ads --bin wai_ad_presentation_vectors -- verify ../live-conformance/presentation
cargo run --no-default-features --features live_ads --bin wai_ad_presentation_vectors -- gen ../live-conformance/presentation # regenerate
cargo test --no-default-features --features live_ads --test ad_presentation
cd ../live-conformance
python3 -I ad_presentation_verify.py presentation
```
`wai_ad_presentation_vectors verify` and `tests/ad_presentation.rs` require every file to
regenerate byte for byte, no other file but this one to be there, and `wai-rs` to give every
case its outcome; the test also requires §11 to list exactly the reasons the reference reports.
`ad_presentation_verify.py` is the second implementation, written from the extension's text and
sharing no code with `wai-rs`: it verifies each room, policy and receipt with
`live_ads_verify.py`, reads and judges every claim itself, verifies RSASSA-PSS with an
implementation of its own, reads §11 from the extension, requires every reason to appear, then
mutates copies of the corpus, one defect each, each of which must fail, and drops each of its
own checks in turn, each weakened verifier of which the corpus must catch. The browser
client's reference producer (`joule-moq-web/web/ads/presentation.test.mjs`) requires
`video_rule`'s first claim from an observation sequence. Pass = an exit status of 0 from each.