A principal other than the room authority may sign WRM1 only under a grant with the record power in effect over the whole span: effective at or before its start, with no leave, grant_exchange or expiry taking the power away before its end. A refusal keeps the code recording_signer_not_permitted and states the first instant of the span without the power, at ["signer", t]. Since a recording holds the room's chains whole, the span now binds the takes the package holds (each start and closing commitment within it) and the takes complete counts, not every context record, so a recorder that gained the power partway seals a span that starts at its grant. New vectors: late_recorder_span (stands), signer_power_late, signer_removed, signer_power_exchanged, signer_grant_expired and signer_grant_revoked (refused at the room: a key-log revocation is not bounded by a time). Co-Authored-By: Claude Opus 5.5 <[email protected]>
128 lines
6.6 KiB
Rust
128 lines
6.6 KiB
Rust
//! `wai.live.recording` (extensions/live-recording.md): the committed corpus
|
|
//! `live-conformance/recordings/vectors.json` regenerates byte for byte from its fixed seeds,
|
|
//! every recording and clip in it gets the outcome it states, every reason the extension
|
|
//! adds appears in it, every take it says it reuses is capture binding's take byte for byte,
|
|
//! and the extension's §9 lists exactly the reasons the reference reports.
|
|
#![cfg(feature = "live_record")]
|
|
|
|
use std::collections::BTreeSet;
|
|
use std::path::PathBuf;
|
|
use wai::live_record::{corpus, Reason};
|
|
|
|
fn wai_dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
|
|
}
|
|
|
|
fn read(p: &str) -> String {
|
|
std::fs::read_to_string(wai_dir().join(p)).unwrap_or_else(|e| panic!("{p}: {e}"))
|
|
}
|
|
|
|
fn committed() -> String {
|
|
read("live-conformance/recordings/vectors.json")
|
|
}
|
|
|
|
#[test]
|
|
fn the_committed_corpus_regenerates_from_its_seeds() {
|
|
assert!(committed() == corpus::generate(), "live-conformance/recordings/vectors.json differs from `wai_live_record_vectors gen`");
|
|
}
|
|
|
|
#[test]
|
|
fn every_vector_gets_its_stated_outcome() {
|
|
let fails = corpus::verify(&committed());
|
|
assert!(fails.is_empty(), "{}", fails.join("\n"));
|
|
}
|
|
|
|
/// Every reason this extension adds is a recording's refusal, a clip's refusal or a
|
|
/// recording's verdict; the good recordings stand; the good clips §10 names are accepted,
|
|
/// the aligned one with twice the jitter as its bound.
|
|
#[test]
|
|
fn the_corpus_covers_every_reason_and_clip() {
|
|
let doc: serde_json::Value = serde_json::from_str(&committed()).unwrap();
|
|
let recordings = doc["recordings"].as_array().unwrap();
|
|
let clips = doc["clips"].as_array().unwrap();
|
|
let mut seen: BTreeSet<String> = BTreeSet::new();
|
|
for v in recordings.iter().chain(clips) {
|
|
if let Some(r) = v["reason"].as_str() {
|
|
seen.insert(r.into());
|
|
}
|
|
if let Some(r) = v["complete"].as_str() {
|
|
seen.insert(r.into());
|
|
}
|
|
}
|
|
for r in Reason::ALL {
|
|
assert!(seen.contains(r.code()), "nothing in the corpus gives {r}");
|
|
}
|
|
for name in ["lesson_session", "signed_by_director", "with_composite", "late_recorder_span"] {
|
|
let v = recordings.iter().find(|v| v["name"] == name).unwrap_or_else(|| panic!("no recording {name}"));
|
|
assert_eq!(v["stands"], true, "{name}");
|
|
assert_eq!(v["complete"], "holds", "{name}");
|
|
}
|
|
for name in ["single_chunk", "byte_range", "whole_take", "first_chunk", "last_chunk", "two_takes_aligned", "live_head"] {
|
|
let v = clips.iter().find(|v| v["name"] == name).unwrap_or_else(|| panic!("no clip {name}"));
|
|
assert_eq!(v["outcome"], "accept", "{name}");
|
|
}
|
|
let aligned = clips.iter().find(|v| v["name"] == "two_takes_aligned").unwrap();
|
|
assert_eq!(aligned["facts"].as_array().unwrap().len(), 2);
|
|
assert_eq!(aligned["bound_us"], 1_000_000);
|
|
let missing = recordings.iter().find(|v| v["name"] == "missing_chunk").unwrap();
|
|
assert_eq!(missing["complete"], "chunk_missing");
|
|
}
|
|
|
|
/// The takes the corpus says it reuses are capture binding's, byte for byte: a recording's
|
|
/// take component holds that corpus's manifest, parts, receipts and consents; and a clip's
|
|
/// part names that corpus's vector only when every commitment it gives is a record of that
|
|
/// vector's chains and every chunk it gives is one of its parts.
|
|
#[test]
|
|
fn the_reused_takes_are_capture_bindings() {
|
|
let doc: serde_json::Value = serde_json::from_str(&committed()).unwrap();
|
|
let takes: serde_json::Value = serde_json::from_str(&read("live-conformance/takes/vectors.json")).unwrap();
|
|
let vector = |name: &str| takes["vectors"].as_array().unwrap().iter().find(|v| v["name"] == name).unwrap_or_else(|| panic!("no take vector {name}")).clone();
|
|
let mut reused = 0;
|
|
for v in doc["recordings"].as_array().unwrap() {
|
|
for r in v["reuses"].as_array().unwrap() {
|
|
let t = vector(r["vector"].as_str().unwrap());
|
|
let dir = format!("takes/{}/", r["take"].as_str().unwrap());
|
|
let files = v["files"].as_object().unwrap();
|
|
assert_eq!(t["take"], r["take"], "{}", v["name"]);
|
|
assert_eq!(files[&format!("{dir}manifest.wtm")], t["manifest"], "{}", v["name"]);
|
|
let mut parts: Vec<(u64, &serde_json::Value)> = files
|
|
.iter()
|
|
.filter_map(|(p, b)| Some((p.strip_prefix(&format!("{dir}chunk-"))?.strip_suffix(".bin")?.parse().ok()?, b)))
|
|
.collect();
|
|
parts.sort_by_key(|p| p.0);
|
|
let want: Vec<(u64, &serde_json::Value)> = t["parts"].as_array().unwrap().iter().map(|p| (p["index"].as_u64().unwrap(), &p["bytes"])).collect();
|
|
assert_eq!(parts, want, "{}", v["name"]);
|
|
let of = |pre: &str| files.iter().filter(|(p, _)| p.starts_with(&format!("{dir}{pre}"))).map(|(_, b)| b.clone()).collect::<Vec<_>>();
|
|
assert_eq!(serde_json::Value::Array(of("receipt-")), t["receipts"], "{}", v["name"]);
|
|
assert_eq!(serde_json::Value::Array(of("consent-")), t["consents"], "{}", v["name"]);
|
|
reused += 1;
|
|
}
|
|
}
|
|
for c in doc["clips"].as_array().unwrap() {
|
|
for p in c["parts"].as_array().unwrap() {
|
|
let Some(name) = p["take_vector"].as_str() else { continue };
|
|
let t = vector(name);
|
|
let records: BTreeSet<&str> = t["chains"].as_array().unwrap().iter().flat_map(|ch| ch.as_array().unwrap().iter().map(|r| r.as_str().unwrap())).collect();
|
|
let parts: BTreeSet<&str> = t["parts"].as_array().unwrap().iter().map(|p| p["bytes"].as_str().unwrap()).collect();
|
|
for r in p["commits"].as_array().unwrap() {
|
|
assert!(records.contains(r.as_str().unwrap()), "clip {}: a commitment not in {name}", c["name"]);
|
|
}
|
|
for b in p["bytes"].as_array().unwrap().iter().filter_map(|b| b.as_str()) {
|
|
assert!(parts.contains(b), "clip {}: a chunk not in {name}", c["name"]);
|
|
}
|
|
reused += 1;
|
|
}
|
|
}
|
|
assert!(reused > 0);
|
|
}
|
|
|
|
/// §9 of the extension lists exactly the codes `Reason::ALL` has, each once, in order.
|
|
#[test]
|
|
fn the_extension_lists_the_reasons() {
|
|
let text = read("extensions/live-recording.md");
|
|
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 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> = Reason::ALL.iter().map(|r| r.code().to_string()).collect();
|
|
assert_eq!(listed, want, "§9's table and Reason::ALL differ");
|
|
}
|