# live-conformance/stitched — stitched presentation The corpus of [`extensions/stitched-presentation.md`](../../extensions/stitched-presentation.md) §10: streams with ads another party stitched into their segments or served as interstitials; the manifests the players read (HLS Media Playlists, an asset list, an MPD); the ad servers' responses (VAST documents and a form-1 `wai.video.ssai` receipt); the `WSI1` pod-input record each break's signer signed; the `WSC1` claims the players signed, good ones and a control of each check; what a verifier states of them. **Presentation equivalence.** For each vector, a conforming verifier given its `records` and `claims`, the files its `manifests`, `responses` and `receipts` name, and the corpus's `creatives`, `issuers` and `signers` (§7.1), states exactly: - `record_facts`, each record in the order given: its `digest` (`null` when it does not read) and its `verdict`, `accepted` or a reason; for an accepted record, `response_checked`, its `ads` (each `slot`, its `own_claims` by index, `presentations`, `verdict`, `quartiles`, `presented_spend_micros` and `stated_quartile`), its `presented_spend_micros`, its `stated_spend_micros`, its frame's `share_bp` and ad `region` (rectangles `[x, y, w, h]`, `null` for the full picture), and, for a record of version 2, its `consent` (`gpp`, `gpp_sid`, `child_directed`); - `claim_facts`, each claim in the order given: its `digest`, its `verdict`, `presented` or a reason, the `record` and `slot` it names (from §7.3 step 4 on), and, for an own claim, the `quartile` it reaches, its `run_ms` and its `viewable_ms`; - `spend`, the presented spend by currency; - for a vector that states a `channel`, a verification of that channel (§7.6) under the `bindings` the vector gives (each a `signer` key and a `channel` id, hexadecimal): its `channel`, which it states as it is given. `record_about` and `claim_about` say what each record and claim is, and are not checked. The files a vector's `bound` names are documents a record binds by digest and no verifier reads (a winning bid, a VMAP response); they are here so that anyone recomputes the digests. A verifier that states another verdict or fact does not conform. **Provenance.** Generated here: `wai_stitched_presentation vectors gen` writes every file from streams authored in code, from fixed seeds. Each session key is SHA-256 of `wai:stitched-presentation/corpus/player`, a zero byte and its number (`players` lists their public keys); each signer's key the same with `signer` (signers 1 and 2 are the trusted ones, `signers`; signer 9 is not); each channel id SHA-256 of `wai:stitched-presentation/corpus/channel`, a zero byte and the channel's name (`A`, `B`, `C`); 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 attestation issuer's key is the test key RFC 9578 Appendix A.2 publishes, as in [`../presentation/`](../presentation/). Every playlist, asset list, MPD, VAST document, bid document and receipt is a fixture written for its case under example domains; none was fetched from a service. Ed25519, BLAKE3 and RSASSA-PSS over a given salt are deterministic, so every file is the same bytes on every machine. The generator asserts that each record and claim gets the verdict it was written to have and that the corpus gives every reason of §9, before it writes. No recorded data. ## Layout - `vectors.json`: `creatives`, each its `objects` (`content_hash`, `duration_ms`); `issuers`; `signers` (Ed25519 public keys, hexadecimal); `players` (informative); and `vectors`. - `NAME.*`: each vector's documents: `.m3u8` Media Playlists, `.assets.json` an asset list, `.mpd` an MPD, `.vast.xml` VAST 4.3 responses, `.vmap.xml` a VMAP response, `.bid.json` a winning bid's document, `.receipt.json` a form-1 receipt in its JSON carriage (§4.5). The vectors: - `hls_stitched`: two viewers' personalised playlists, each ad under a date range whose `X-AD-ID` carries its `AdServingId`; a record per viewer priced from its VAST `Pricing` (CPM 25.00, 18.00 and 30.00 USD). Presented: viewer 1's two ads (the second to its third quartile) and viewer 2's first. Viewer 2's second ad was fetched ahead of the break and never presented: `claim_absent`, no spend. Record controls: `pod_signer_unknown`, `pod_response_mismatch` (a price the response does not give; a universal creative identifier it does not carry), `pod_duplicate`, `pod_input_malformed`, `bad_signature`. Claim controls: `claim_duplicate`, `claim_key_unbound`, `claim_session_unbound`, `claim_no_decision`, `claim_manifest_unknown`, `claim_marker_absent`, `stitched_claim_malformed` (truncated; a signed skip with no resumption), `bad_signature`. - `hls_interstitial`: one shared playlist with an interstitial date range, and the asset list a viewer was served; records under the cross-media rule with an audible run and attestation, ad-i1 at a winning bid's price (its document bound by digest), ad-i2 at CPM 20.00. Presented: viewer 11's two, attested on a television. Controls: `attestation_absent`, `attestation_invalid` (another presentation's token). - `squeezeback`: a squeeze on a DASH channel, the program in a 1440 × 810 window in the bottom right corner and the ad in the L around it (4375 basis points of the picture), marked by an event whose `messageData` carries the ad's `AdServingId`; one shared record. Presented: viewers 21, 22 (70% of the region in view) and 23 (to the midpoint): three presentations. Controls: `claim_frame_mismatch`, `claim_not_viewable`, `claim_duplicate`, `claim_asset_mismatch`. - `double_box`: a double box on an HLS channel, its record in EUR at a price the signer states, its VMAP response bound and not read. Presented: viewer 31 over the ad's box, viewer 32 over the player's area. - `form1_prefetch`: a stitcher's own decision sealed in a form-1 receipt whose beacons counted ad-f2 as started when its first segment was fetched ahead of the break; viewer 41 left after ad-f1. Stated: quartiles 5 and 1, 5 000 000 µ-units; presented: ad-f1 alone, 3 000 000. Control: `pod_response_mismatch` (the same receipt at another price). - `consent`: two viewers' personalised playlists, each with one stitched ad, and a record of version 2 per viewer (§4.7): viewer 51's carries the GPP string its player gave, the example the GPP string's text gives for a service reached by a URL, with section 2; viewer 52's no string and a child-directed request. Presented: both. Controls: viewer 53's record with its GPP string changed after signing (`bad_signature`), whose ad's claim then names no decision (`claim_no_decision`); a shared record carrying a viewer's string, and a record stating a section with no string (each `pod_input_malformed`). - `channel`: channel A's verification (§7.6), one account's linear HLS channel with two shared breaks of one ad each, as its live playlist carried each, and one VAST response for both. The bindings pair signer 1 with channels A and C and name signer 2 nowhere. Presented: the 12:05 break's ad-a, signed with signer 1's key, by viewers 61 and 62 (5 000 000 µ-units). Control: the 12:10 break's record, signed with signer 2's key, `pod_channel_unbound`, and viewer 61's claim of its ad `claim_no_decision`. - `channel_other_key`: channel B's verification, the account's second channel, the bindings pairing signer 1 with channel A and signer 2 with channel B. Presented: the 12:05 break's ad-c, signed with signer 2's key, by viewer 63 (3 000 000). Control: the 12:10 break signed with channel A's key, `pod_channel_unbound` though that key is among the signers, and viewer 63's claim of its ad `claim_no_decision`. ## Running ``` cd ../../wai-rs cargo run --no-default-features --features stitched_presentation --bin wai_stitched_presentation -- vectors verify ../live-conformance/stitched cargo run --no-default-features --features stitched_presentation --bin wai_stitched_presentation -- vectors gen ../live-conformance/stitched # regenerate cargo test --no-default-features --features stitched_presentation --test stitched_presentation cd ../live-conformance python3 -I stitched_presentation_verify.py stitched node --test ../../joule-code/joule-code-rs/crates/joule-moq-web/web/ads/stitched.test.mjs ``` `vectors verify` and `tests/stitched_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 vector its facts; the test also requires §9 to list exactly the codes the reference adds. `stitched_presentation_verify.py` is the second implementation, written from the extension's text and sharing no code with `wai-rs`: it reads every manifest, response, record and claim itself (with the spans' quartile and run and RSASSA-PSS of `ad_presentation_verify.py`, a form-1 receipt's leaves of `live_ads_verify.py` and the ad frame of `live_compose_verify.py`), 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 requires the corpus's claims from the corpus's manifests and observation sequences. Pass = an exit status of 0 from each. ## The tool's inputs `wai_stitched_presentation verify ` (and `POST /v1/verify` of the service) reads one JSON object: | member | value | |---|---| | `records` | the pod-input records, hexadecimal | | `claims` | the claims, hexadecimal, at most 2^20 | | `manifests` | the manifests' texts, as strings | | `responses` | the VAST and VMAP documents' texts, as strings | | `receipts` | form-1 receipts in their JSON carriage (§4.5), as objects | | `creatives` | as `vectors.json`'s | | `issuers` | as `vectors.json`'s | | `signers` | the accepted signers' keys, hexadecimal | | `channel` | optional: the channel id of a verification of a channel (§7.6), hexadecimal | | `bindings` | with a `channel`: the bindings, each `{"signer", "channel"}`, hexadecimal | and prints `record_facts`, `claim_facts` and `spend` as this corpus states them, and the `channel` of a verification of one, with the `capability`. `wai_stitched_presentation sign-pod --key ` reads a draft of a record: the fields of §4.1 by name (`carriage`, `session` in hexadecimal or absent for a shared break, `avail_id`, `avail_ms`, `response_format`, `response_file` (a path beside the draft whose SHA-256 is taken) or `response` (hexadecimal), `id_kind`, `id_attr`, `currency`, `exponent`, `frame` (its fields, or absent for the full picture) and `requirement` (`require`, `rule`, `token_type`, `issuer`)), and `ads`, each with `marker` as its fields (`id` and `start` for carriage 1; and `index` for 2; `period`, `scheme`, `value` and `id` for 3), `ad_id`, `serving_id`, `creative_id`, `registry`, `creative_hash`, `duration_ms`, `price_micros`, `price_source` and `evidence`; and, for a record of version 2, `consent`, as live-ads §8.6's JSON carriage writes it (`gpp`, `gpp_sid`, `child_directed`). It refuses a draft that breaks §4.4, and prints the signed record in hexadecimal.