Some checks failed
joule-moq-http live HTTP egress / joule-moq-http · does this change touch it (push) Successful in 28s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 21s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 23s
wai-audit collector and player integration / does this change touch it (push) Successful in 21s
wai-app and Create / wai-app · does this change touch it (push) Successful in 21s
wai-fleet service / does this change touch it (push) Successful in 23s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 24s
wai-quantum studio and desktop / does this change touch it (push) Successful in 51s
WAI Studio / does this change touch it (push) Successful in 25s
wai-ledger service / does this change touch it (push) Successful in 6m25s
wai-pack / wai-pack · does this change touch it (push) Successful in 6m17s
wai-sink-swift native sink package / does this change touch it (push) Successful in 6m6s
wai-quantum for JavaScript / does this change touch it (push) Successful in 6m18s
WAI operation catalogue / does this change touch it (push) Successful in 6m28s
wai-sink-android native sink library / does this change touch it (push) Successful in 6m9s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 11s
WAI examples / WAI examples · does this change touch them (push) Successful in 8s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 45s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m4s
wai-fleet service / service · linux (push) Successful in 2m30s
wai-audit collector and player integration / collector · linux (push) Successful in 4m53s
wai-quantum studio and desktop / studio · browser (push) Successful in 4m35s
wai-ledger service / service · linux (push) Successful in 2m36s
WAI operation catalogue / the catalogue and every door · linux (push) Failing after 3m13s
wai-pack / wai-pack · linux (push) Successful in 4m24s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 7m47s
wai-quantum for JavaScript / javascript · linux (push) Successful in 10m44s
WAI Studio / Studio in WebKit · linux (push) Failing after 17m40s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Failing after 9m38s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 1m52s
joule-moq-http live HTTP egress / joule-moq-http, every target (push) Successful in 10m25s
wai-pack / wai-pack · linux-arm64 (push) Successful in 5m38s
joule-moq-room room service / joule-moq-room, every target (push) Failing after 8m36s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 8m36s
wai-pack / wai-pack · independent verifier (Python) (push) Successful in 25m47s
wai-pack / wai-pack · dependency licences (push) Failing after 29m20s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 1m23s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m1s
wai-fleet service / service · macos (push) Successful in 56s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Failing after 7m31s
wai-ledger service / service · macos (push) Failing after 1m23s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 8m4s
wai-pack / wai-pack · macos (push) Successful in 3m58s
WAI Studio / Studio in Chromium and Firefox · macos (push) Failing after 12m13s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 15m20s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 14m54s
wai-pack / wai-pack · the floor through a media prober (push) Failing after 1h3m51s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Failing after 38m8s
wai-quantum studio and desktop / desktop app · windows (push) Has been cancelled
WAI examples / WAI examples · portable · linux (push) Has been cancelled
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Has been cancelled
WAI examples / WAI examples · android-app · linux-arm64 (push) Has been cancelled
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Has been cancelled
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Has been cancelled
stitched-presentation §7.6 (new), §2, §7.1, §7.2 step 3, §7.5, §9, §10, §11. A verifier may be given a channel, a 32-byte channel id the parties name it by, and the bindings the buyer and the seller agreed beside the signers, each a signer's key and a channel id. A verification of a channel accepts a record only when a binding pairs its signer with that channel, and refuses any other pod_channel_unbound (new, §9) at step 3, after pod_signer_unknown: a key bound to other channels alone, and a key bound to none, so a binding left out refuses records rather than admitting them. The verification states its channel id. A party that signs many channels' breaks for one buyer under a key for each can no longer have one channel's record counted in another channel's verification under the other channel's key. WSI1 keeps its layout and its signature; a verification of no channel reads no binding and states what it stated before; the receipt ledger bills a stitched source per signer, as before. Reference: stitched_presentation::Channel (admits), verify_channel, verify_in, Verification::channel, Reason::PodChannelUnbound; the tool's verify and the service's POST /v1/verify read a bundle's channel and bindings. Corpus live-conformance/stitched/ gains two vectors: channel (channel A's verification; a key bound to two channels accepted, a key bound to none refused, its ad's claim then naming no decision) and channel_other_key (channel B's; a key bound to B accepted, B's break signed with channel A's key refused though that key is among the signers). It regenerates byte for byte on x86_64 (Framework) and aarch64 (Thor), and wai-rs gives every vector its facts. Second implementation stitched_presentation_verify.py mirrors the rule: every vector agrees, every reason of §9 is given, 30 of 30 mutated copies fail (six new: a key bound to the channel added, the channel changed, a binding's channel changed, the bindings dropped, the channel dropped, a refused record stated accepted) and 18 of 18 weakened verifiers are caught (the channel check among them). The ad-traffic and compose-ad-track second implementations, which read it, still pass; so do the receipt-ledger, impression-aggregate, compose-ad-track, channel-lineup, revenue-share and opportunity-report corpus tests that consume this module. Planted faults, each caught by tests/stitched_presentation.rs: the channel check dropped; an unbound key admitted; a binding read without its signer; a binding read without its channel; the channel not stated; the check limited to one response format. Decision D54: the binding is the parties' agreement read with the signers, not a channel id in the record, fail closed, and the ledger needs nothing. Four API breaks acknowledged in the change note (a new Reason variant, ADDED and ALL one longer, Verification::channel). Co-Authored-By: Claude Opus 5.5 <[email protected]>
159 lines
11 KiB
Markdown
159 lines
11 KiB
Markdown
# 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 <bundle.json>` (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 <draft.json> --key <seed-file>` 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.
|