extensions/takedown.md registers wai.media.takedown: a service's signed takedown of a source (object, group, track or take) under a policy that pins a registry of regime clocks (registry/takedown-clocks.json, read from Public Law 119-12 s3(a)(3), the IT Rules 2021 rule 3(1)(d) and 3(2)(b) as amended by G.S.R. 120(E), Regulation (EU) 2024/1083 Art. 18(4) and Regulation (EU) 2022/2065 Art. 16(6)). The closure is computed from deriving-edge records, synthesis origin claims and group receipts and held to the takedown's stated set; perceptual matches are a class apart; holders sign purges and the time to removal is held to the clock (purge_missing, deadline_missed, window_not_observed); counts per clock, ground, category and source recompute. The wai-rs module (feature takedown), its corpus (live-conformance/takedown, 31 cases and 2 counts) and live-conformance/takedown_verify.py agree on every case. Co-Authored-By: Claude Opus 5.5 <[email protected]>
67 lines
3.2 KiB
Rust
67 lines
3.2 KiB
Rust
//! `wai.media.takedown` (extensions/takedown.md): the committed corpus
|
|
//! `live-conformance/takedown/` regenerates byte for byte from its fixed seeds, holds no other
|
|
//! file, every case in it gets the outcome it states, the extension's §11 lists exactly the
|
|
//! reasons the reference reports, and the registry the corpus carries is the one the repository
|
|
//! publishes. The generator itself asserts that the corpus gives every one of them.
|
|
#![cfg(feature = "takedown")]
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::path::PathBuf;
|
|
use wai::takedown::{corpus, read_registry, Reason, REGISTRY};
|
|
|
|
fn wai_dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
|
|
}
|
|
|
|
fn committed() -> BTreeMap<String, String> {
|
|
let dir = wai_dir().join("live-conformance/takedown");
|
|
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_takedown_vectors gen`");
|
|
}
|
|
|
|
#[test]
|
|
fn every_case_gets_its_stated_outcome() {
|
|
let fails = corpus::verify(&committed());
|
|
assert!(fails.is_empty(), "{}", fails.join("\n"));
|
|
}
|
|
|
|
/// §11 of the extension lists exactly the codes `Reason::ALL` has, each once, in order.
|
|
#[test]
|
|
fn the_extension_lists_the_reasons() {
|
|
let text = std::fs::read_to_string(wai_dir().join("extensions/takedown.md")).expect("the extension");
|
|
let at = text.find("\n## 11. Reasons").expect("a §11");
|
|
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, "§11's table and Reason::ALL differ");
|
|
}
|
|
|
|
/// The registry the module carries is the file the repository publishes, and every clock in it
|
|
/// cites an instrument and a provision.
|
|
#[test]
|
|
fn the_registry_is_the_published_file() {
|
|
let file = std::fs::read_to_string(wai_dir().join("registry/takedown-clocks.json")).expect("the registry");
|
|
assert_eq!(file, REGISTRY);
|
|
let r = read_registry(&file).expect("it reads under §4");
|
|
let v: serde_json::Value = serde_json::from_str(&file).unwrap();
|
|
for c in v["clocks"].as_array().unwrap() {
|
|
assert!(!c["instrument"].as_str().unwrap().is_empty() && !c["section"].as_str().unwrap().is_empty());
|
|
}
|
|
assert_eq!(r.clocks.len(), v["clocks"].as_array().unwrap().len());
|
|
}
|