wai.video.stitched_presentation (extensions/stitched-presentation.md): a WPS1 claim names an ad by the marker the stream carries for it (an EXT-X-DATERANGE tag, an interstitial's asset, a DASH event) in the manifest it states the SHA-256 of; a WPI1 pod-input record, signed by the stitcher or a proxy in front of the ad server, binds the response digest, the chosen ads, their creatives and prices, the avail, the frame and the measurement terms; a verifier recomputes each ad's presentations, quartiles and the spend that counts by currency. A form-1 wai.video.ssai receipt gains a JSON carriage (its signed bytes unchanged) so it can be a record's response. live-compose gains the squeeze and double-box ad frames (§5.9). Corpus live-conformance/stitched/, its independent verifier stitched_presentation_verify.py, and the browser producer web/ads/stitched.js with tests against the corpus. Co-Authored-By: Claude Opus 5.5 <[email protected]>
55 lines
2.7 KiB
Rust
55 lines
2.7 KiB
Rust
//! `wai.video.stitched_presentation` (extensions/stitched-presentation.md): the committed corpus
|
|
//! `live-conformance/stitched/` regenerates byte for byte from its fixed seeds, holds no other
|
|
//! file, every vector gets the facts it states, and the extension's §9 lists exactly the
|
|
//! reasons it adds. The generator itself asserts that every record and claim gets the verdict
|
|
//! it was written to get, and that the corpus gives every reason.
|
|
#![cfg(feature = "stitched_presentation")]
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::path::PathBuf;
|
|
use wai::stitched_presentation::corpus;
|
|
use wai::stitched_presentation::Reason;
|
|
|
|
fn wai_dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
|
|
}
|
|
|
|
fn committed() -> BTreeMap<String, String> {
|
|
let dir = wai_dir().join("live-conformance/stitched");
|
|
std::fs::read_dir(&dir)
|
|
.expect("the committed corpus")
|
|
.flatten()
|
|
.map(|e| (e.file_name().to_string_lossy().into_owned(), e.path()))
|
|
.filter(|(n, _)| n != "README.md")
|
|
.map(|(n, p)| (n, std::fs::read_to_string(p).expect("a text file")))
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn the_committed_corpus_regenerates_from_its_seeds() {
|
|
let files = committed();
|
|
let generated: BTreeMap<String, String> = corpus::generate().into_iter().collect();
|
|
let extra: Vec<&String> = files.keys().filter(|k| !generated.contains_key(*k)).collect();
|
|
let missing: Vec<&String> = generated.keys().filter(|k| !files.contains_key(*k)).collect();
|
|
let differ: Vec<&String> = generated.iter().filter(|(k, v)| files.get(*k).is_some_and(|f| f != *v)).map(|(k, _)| k).collect();
|
|
assert!(extra.is_empty() && missing.is_empty() && differ.is_empty(), "extra {extra:?}, missing {missing:?}, differ {differ:?}: run `wai_stitched_presentation vectors gen`");
|
|
}
|
|
|
|
#[test]
|
|
fn every_vector_gets_its_stated_facts() {
|
|
let fails = corpus::verify(&committed());
|
|
assert!(fails.is_empty(), "{}", fails.join("\n"));
|
|
}
|
|
|
|
/// §9 of the extension lists exactly the codes `Reason::ADDED` has, each once, in order.
|
|
#[test]
|
|
fn the_extension_lists_the_reasons() {
|
|
let text = std::fs::read_to_string(wai_dir().join("extensions/stitched-presentation.md")).expect("the extension");
|
|
let at = text.find("\n## 9. Reasons").expect("a §9");
|
|
let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e);
|
|
let table = &text[at..end];
|
|
let added = table.split("\nA verification also").next().expect("the table");
|
|
let listed: Vec<String> = added.lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect();
|
|
let want: Vec<String> = Reason::ADDED.iter().map(|r| r.code().to_string()).collect();
|
|
assert_eq!(listed, want, "§9's table and Reason::ADDED differ");
|
|
}
|