open-standards/wai/wai-rs/tests/impression_aggregate.rs
Transaction Science 61320c8641 A report an aggregator committed and was sent again is not rejected, and a reader takes one statement of no count
telemetry-aggregation §6.1's rule that `rejected` counts no report the aggregator aggregated had no
test, and impression_aggregate_verify.py did not apply it. The flight flow now sends the helper a
report it committed, and the Python flow filters as the Rust does. impression-aggregates §7.3 says
what a reader does with a statement of version 1 beside one of version 2, and the corpus adds a
receipt and a table of each.

The ledger's corpus takes these receipts, so it is regenerated by the commit after this one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 08:35:01 -05:00

132 lines
8.2 KiB
Rust

//! `wai.delivery.impressions` (extensions/impression-aggregates.md): the committed corpus
//! `live-conformance/impressions/` regenerates byte for byte from its fixed seeds, holds no other
//! file, every case in it gets the outcome it states, §10 lists exactly the reasons the reference
//! reports, each standing receipt's result is its contributions' tally and stitched presentation's
//! own count of the flight's claims (§8.11), and the size bounds of §5.4 and §6.4 decide a
//! file's outcome at their edge.
#![cfg(feature = "impression_aggregate")]
use std::collections::BTreeMap;
use std::path::PathBuf;
use wai::impression_aggregate::{
accept, corpus, encode_hpke_list, TaskFile, UploadFile, CATALOGUE_MALFORMED, CODES, MAX_TASK_FILE, MAX_UPLOAD, TASK_FILE_MALFORMED, UPLOAD_MALFORMED,
};
use wai::telemetry_aggregate::dap::{HpkeCiphertext, HpkeConfig, Report, ReportMetadata};
fn wai_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
}
fn committed() -> BTreeMap<String, String> {
let dir = wai_dir().join("live-conformance/impressions");
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_impression_aggregate_vectors gen`");
}
#[test]
fn every_case_gets_its_stated_outcome() {
let fails = corpus::verify(&committed());
assert!(fails.is_empty(), "{}", fails.join("\n"));
}
/// §8.11: each standing receipt's result is the plaintext tally of its contributions, and the
/// flight's table gives per creative what stitched presentation's verifier gives.
#[test]
fn aggregation_reproduces_the_plaintext_and_stitched_counts() {
let fails = corpus::equivalence();
assert!(fails.is_empty(), "{}", fails.join("\n"));
}
/// telemetry-aggregation §6.1, §7.3 here: each aggregator states its own `rejected` and the two
/// differ on the flight's first hour, the leader counting what its helper rejected as well; a table
/// carries each, a statement of version 1 states none, and a count changed after its aggregator
/// signed is refused at that aggregator.
#[test]
fn each_aggregator_states_what_it_rejected() {
let doc: serde_json::Value = serde_json::from_str(&committed()["vectors.json"]).unwrap();
let case = |kind: &str, name: &str| doc[kind].as_array().unwrap().iter().find(|c| c["name"] == name).unwrap_or_else(|| panic!("no case {name}"))["outcome"].clone();
let pair = |v: &serde_json::Value| (v["leader"].as_u64(), v["helper"].as_u64());
let r0 = case("receipts", "r0");
// b0's seven failures reach the leader; three reach the helper and are refused there.
assert_eq!(pair(&r0["rejected"]), (Some(7), Some(3)));
let swapped = case("receipts", "r0_rejected_as_each_states");
assert_eq!((swapped["outcome"].as_str(), pair(&swapped["rejected"])), (Some("accept"), (Some(0), Some(43))), "nothing holds one count to the other");
let none = case("receipts", "r0_of_version_1");
assert_eq!((none["outcome"].as_str(), pair(&none["rejected"])), (Some("accept"), (None, None)));
let changed = case("receipts", "rejected_count_changed");
assert_eq!((changed["reason"].as_str(), changed["at"].as_str()), (Some("bad_signature"), Some("helper")));
let table = case("tables", "flight");
assert_eq!(pair(&table["receipts"][0]["rejected"]), (Some(7), Some(3)));
assert_eq!(pair(&table["receipts"][1]["rejected"]), (Some(0), Some(0)));
assert_eq!(pair(&table["totals"]["rejected"]), (Some(7), Some(3)));
let unstated = case("tables", "flight_a_statement_of_no_count");
assert_eq!(pair(&unstated["totals"]["rejected"]), (None, None), "a total of what one statement does not state is none, never a part");
// A reader tolerates a statement of version 1 beside one of version 2: each count is its own.
let both = case("receipts", "r0_of_both_versions");
assert_eq!((both["outcome"].as_str(), pair(&both["rejected"])), (Some("accept"), (Some(7), None)));
let half = case("tables", "flight_one_statement_of_no_count");
assert_eq!(pair(&half["receipts"][0]["rejected"]), (Some(7), None));
assert_eq!(pair(&half["totals"]["rejected"]), (Some(7), None), "the leader's total stands beside a helper's of none");
// A report the helper committed and was sent again: refused, and none of what it states.
let flow = doc["flows"].as_array().unwrap().iter().find(|f| f["name"] == "flight").unwrap();
let replay = flow["steps"].as_array().unwrap().iter().find(|s| s["step"] == "replay").expect("a replay");
let replayed = format!("{}02{:02x}", replay["id"].as_str().unwrap(), wai::telemetry_aggregate::dap::report_error::REPORT_REPLAYED);
assert_eq!(replay["resp"].as_str(), Some(replayed.as_str()));
}
/// §10 of the extension lists exactly the codes `CODES` has, each once, in order.
#[test]
fn the_extension_lists_the_reasons() {
let text = std::fs::read_to_string(wai_dir().join("extensions/impression-aggregates.md")).expect("the extension");
let at = text.find("\n## 10. Reasons").expect("a §10");
let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e);
let listed: Vec<String> =
text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect();
let want: Vec<String> = CODES.iter().map(|c| c.to_string()).collect();
assert_eq!(listed, want, "§10's table and CODES differ");
}
/// The size bounds of §5.4 and §6.4, which no vector carries. A file of exactly the bound that
/// reads in every other respect is read: a task file passes step 1 and is refused at step 2,
/// where its catalogue of zeros does not read, and an upload file reads. The same file one byte
/// longer is refused at step 1, so a reader without the bound, or with another, fails here.
/// §4.3's bound decides no outcome: no catalogue that meets §4.3 comes near it.
#[test]
fn the_size_bounds_decide_at_their_edge() {
let list = |id: u8| encode_hpke_list(&[HpkeConfig { id, kem: 0x0020, kdf: 0x0001, aead: 0x0001, public_key: vec![9; 32] }]);
let task_file = |catalogue: usize| {
let f = TaskFile { task: vec![], catalogue: vec![0; catalogue], leader_hpke: list(1), helper_hpke: list(2), signer: [0; 32], signer_id: "x".into(), sig: [0; 64] };
f.to_bytes()
};
let at = MAX_TASK_FILE - task_file(0).len();
let (inside, past) = (task_file(at), task_file(at + 1));
assert_eq!((inside.len(), past.len()), (MAX_TASK_FILE, MAX_TASK_FILE + 1));
assert_eq!(accept(&inside, &[], &[], None).err(), Some(CATALOGUE_MALFORMED), "a task file at the bound reaches step 2");
assert_eq!(accept(&past, &[], &[], None).err(), Some(TASK_FILE_MALFORMED), "one byte past it is refused at step 1");
let ct = HpkeCiphertext { config_id: 1, enc: vec![1], payload: vec![1] };
let upload = |share: usize| {
let metadata = ReportMetadata { id: [0; 16], time: 0, public_extensions: vec![] };
let r = Report { metadata, public_share: vec![0; share], leader_share: ct.clone(), helper_share: ct.clone() };
UploadFile { task_id: [0; 32], reports: vec![r] }.encode()
};
let at = MAX_UPLOAD - upload(0).len();
let (inside, past) = (upload(at), upload(at + 1));
assert_eq!((inside.len(), past.len()), (MAX_UPLOAD, MAX_UPLOAD + 1));
assert_eq!(UploadFile::parse(&inside).map(|f| f.reports.len()).ok(), Some(1), "an upload file at the bound reads");
assert_eq!(UploadFile::parse(&past).err(), Some(UPLOAD_MALFORMED), "one byte past it is refused");
}