All checks were successful
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 1m59s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 3s
no-vanity-metrics / WAI public text names no comparison (pull_request) Successful in 4s
no-vanity-metrics / WAI public text names no company or product (pull_request) Successful in 3s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m8s
standards workspaces build + test / eoc-rs (pull_request) Successful in 2m0s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m30s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m9s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m12s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m12s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m18s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m9s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m49s
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Successful in 28s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 7m57s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 20s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 13m52s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 9s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 29s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 54s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 1m58s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 1m10s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 1m6s
WAI neural decoders end-to-end / synthetic fixtures → Rust decode → reference (±4) (pull_request) Successful in 1m11s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m12s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 4s
WAI section references / cited sections exist (pull_request) Successful in 10s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 33m46s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 4m40s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 2m53s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 4s
standards workspaces build + test / aac-rs (pull_request) Successful in 9m8s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 3m2s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 4m3s
standards workspaces build + test / map-rs (pull_request) Successful in 7m9s
standards workspaces build + test / mesh-rs (pull_request) Successful in 4m14s
standards workspaces build + test / openpay (pull_request) Successful in 31m58s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Successful in 1m11s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 10m31s
WAI HTTP delivery / HTTP corpus · falsification and differential (pull_request) Successful in 1h31m39s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 2m1s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 3m36s
wai-pack / wai-pack · linux (pull_request) Successful in 1m7s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 24s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 1m47s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m21s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 3m18s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 3m10s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m16s
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Successful in 30s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 12m1s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 31s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 3m31s
wai-pack / wai-pack · macos (pull_request) Successful in 2m11s
SECURITY.md: - "Allocation follows the input" was false for the manifest. The reference readers parse it into serde_json::Value; measured on the release build, a 94 MB prev of 2^20 runs peaks at about 7 times its length and a 134,217,000-byte array of zeros at about 38 times (5.1 GB RSS, 7.6 s in from_bytes, which returned Ok). The text now says so, and the argument that a sink short of that memory could not decode such layers either is gone. - CI does link ONNX Runtime and libzstd: wai-neural-gates.yml and wai-neural-e2e.yml build `neural`. No workflow builds `codecs`. - No Kani harness is in int_kernels.rs; the harnesses are in rans.rs, int_entropy.rs, int_transform.rs, wai_video_policy.rs, staged/predict.rs and staged/wir1_arith.rs. Its SIMD kernels, the only unsafe outside the C ABI, are held to the scalar kernels by equality tests, not by a model checker. - The bare "(§5.1)" names http-delivery; the writer test is described as it now runs; the Python verifiers' checks are described as they now run. SPEC §2: the 2^20-unit cap is staged-delivery Appendix A.3's (section 3 bounds a count only by 32 bits); the limit bounds what a reader reads, not the memory its parse takes; the paragraph is reflowed. SPEC §6.5: Lane A's records are "among them", and include wai.video.ssai and wai.video.channel. Lane B no longer says every record applies: staged-measurement counts no encrypted object, and a verifier without the key checks no payload-dependent rule of a reconstruct-session claim (jwp-receipts §2.4.12 rule 3); nothing in the reference states a Lane B tier. The confidentiality header and the CHANGELOG say the same. SPEC §5 and determinism-tiers §5: only `photo` comes from a trained model; gradient, bloom, facade and the cross-architecture golden use seeded integer weights, as the CHANGELOG said both texts did. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1629 lines
88 KiB
Rust
1629 lines
88 KiB
Rust
//! Native-sink E2E: decode every shipped `.wai` neural sample through
|
||
//! the Rust `ort`-backed runtime. Asserts the decoder produces sane
|
||
//! output (right sample count for audio, right pixel grid for image,
|
||
//! right frame count for video, and a non-trivial signal in each).
|
||
//!
|
||
//! Models are expected at `wai-web/demo/models/<name>/decoder.onnx` —
|
||
//! built via the corresponding `tools/wai_<name>_export_onnx.py` once
|
||
//! per machine (see wai-web/demo/models/README.md).
|
||
//!
|
||
//! Skipped (printed, not failed) when a given .onnx isn't present yet,
|
||
//! so a fresh clone with only some models built still runs the tests
|
||
//! that can run.
|
||
|
||
#![cfg(feature = "neural")]
|
||
|
||
use std::path::{Path, PathBuf};
|
||
|
||
use wai::container::{Conditioning, Manifest, ModelRequirement, Prior, Wai};
|
||
use wai::neural::{
|
||
decode_envelope, decode_envelope_strict, model_sha256, DecodeError, Decoded, ModelRegistry,
|
||
};
|
||
|
||
/// The reason a pinned decode was refused with, or a description of what happened.
|
||
fn refusal(r: Result<Decoded, DecodeError>) -> String {
|
||
match r {
|
||
Err(DecodeError::PriorMismatch(why)) => why.code().to_string(),
|
||
Err(e) => format!("other error: {e}"),
|
||
Ok(_) => "decoded".into(),
|
||
}
|
||
}
|
||
|
||
fn repo_root() -> PathBuf {
|
||
Path::new(env!("CARGO_MANIFEST_DIR")).join("..").canonicalize().unwrap()
|
||
}
|
||
|
||
fn model_path(name: &str) -> PathBuf {
|
||
repo_root().join(format!("wai-web/demo/models/{name}/decoder.onnx"))
|
||
}
|
||
|
||
fn sample_path(name: &str) -> PathBuf {
|
||
repo_root().join(format!("wai-web/demo/samples/{name}"))
|
||
}
|
||
|
||
fn load_envelope(name: &str) -> Wai {
|
||
let bytes = std::fs::read(sample_path(name))
|
||
.unwrap_or_else(|e| panic!("read {name}: {e}"));
|
||
Wai::from_bytes(&bytes).unwrap_or_else(|e| panic!("parse {name}: {e}"))
|
||
}
|
||
|
||
fn registry() -> ModelRegistry {
|
||
ModelRegistry::new()
|
||
.register("wai.neural.encodec32", model_path("encodec_32khz"))
|
||
.register("wai.neural.dac", model_path("dac_44khz"))
|
||
.register("wai.neural.mimi", model_path("mimi"))
|
||
.register("wai.neural.wavtokenizer", model_path("wavtokenizer"))
|
||
.register("wai.neural.bmshj2018", model_path("bmshj2018"))
|
||
.register("wai.neural.video_bmshj2018", model_path("bmshj2018")) // shares the image decoder
|
||
}
|
||
|
||
fn assert_audio_sane(cap: &str, sample_file: &str, expected_sr: u32, min_samples: usize) {
|
||
let onnx = model_path(match cap {
|
||
"wai.neural.encodec32" => "encodec_32khz",
|
||
"wai.neural.dac" => "dac_44khz",
|
||
"wai.neural.mimi" => "mimi",
|
||
"wai.neural.wavtokenizer" => "wavtokenizer",
|
||
_ => unreachable!(),
|
||
});
|
||
if !onnx.exists() {
|
||
eprintln!("skipping {cap}: {} not built (see tools/wai_<name>_export_onnx.py)",
|
||
onnx.display());
|
||
return;
|
||
}
|
||
let env = load_envelope(sample_file);
|
||
assert_eq!(env.manifest.model_requirement.capability, cap,
|
||
"{sample_file} declares wrong capability");
|
||
let dec = decode_envelope(&env, ®istry())
|
||
.unwrap_or_else(|e| panic!("decode {sample_file}: {e}"));
|
||
match dec {
|
||
Decoded::Audio(audio) => {
|
||
assert_eq!(audio.sample_rate, expected_sr,
|
||
"{cap}: wrong sample rate");
|
||
assert!(audio.samples.len() >= min_samples,
|
||
"{cap}: only {} samples, expected ≥ {}",
|
||
audio.samples.len(), min_samples);
|
||
// Non-trivial signal: peak amplitude well above 0.
|
||
let peak = audio.samples.iter().fold(0f32, |a, &b| a.max(b.abs()));
|
||
assert!(peak > 0.01, "{cap}: peak={peak:.4} (silent decode?)");
|
||
// No NaNs.
|
||
assert!(audio.samples.iter().all(|s| s.is_finite()),
|
||
"{cap}: NaN/Inf in output");
|
||
println!(" {cap}: {} samples @ {} Hz, peak {:.3}",
|
||
audio.samples.len(), audio.sample_rate, peak);
|
||
}
|
||
_ => panic!("{cap}: expected Decoded::Audio"),
|
||
}
|
||
}
|
||
|
||
#[test] fn encodec32_decodes_natively() {
|
||
assert_audio_sane("wai.neural.encodec32", "glass.encodec.wai", 32_000, 16_000);
|
||
}
|
||
#[test] fn dac_decodes_natively() {
|
||
assert_audio_sane("wai.neural.dac", "glass.dac.wai", 44_100, 22_000);
|
||
}
|
||
#[test] fn mimi_decodes_natively() {
|
||
assert_audio_sane("wai.neural.mimi", "glass.mimi.wai", 24_000, 12_000);
|
||
}
|
||
#[test] fn wavtokenizer_decodes_natively() {
|
||
assert_audio_sane("wai.neural.wavtokenizer", "glass.wavtokenizer.wai", 24_000, 12_000);
|
||
}
|
||
|
||
#[test]
|
||
fn bmshj2018_decodes_natively() {
|
||
let onnx = model_path("bmshj2018");
|
||
if !onnx.exists() {
|
||
eprintln!("skipping bmshj2018: {} not built", onnx.display());
|
||
return;
|
||
}
|
||
let env = load_envelope("kodim23.bmshj2018.wai");
|
||
let dec = decode_envelope(&env, ®istry()).expect("bmshj2018 decode");
|
||
match dec {
|
||
Decoded::Image(img) => {
|
||
assert_eq!(img.width, 256);
|
||
assert_eq!(img.height, 256);
|
||
assert_eq!(img.rgb.len(), 256 * 256 * 3);
|
||
// Variance: a real image has lots of pixel variation. A blank
|
||
// decode would be near-constant.
|
||
let mean = img.rgb.iter().map(|&v| v as f64).sum::<f64>() / img.rgb.len() as f64;
|
||
let var = img.rgb.iter()
|
||
.map(|&v| (v as f64 - mean).powi(2)).sum::<f64>() / img.rgb.len() as f64;
|
||
assert!(var > 100.0, "bmshj2018: variance {var:.0} suspiciously low");
|
||
println!(" bmshj2018: 256x256 RGB, mean={mean:.1}, var={var:.0}");
|
||
}
|
||
_ => panic!("bmshj2018: expected Decoded::Image"),
|
||
}
|
||
}
|
||
|
||
/// Determinism is half the `Prior.determinism` contract: a "replicate" claim
|
||
/// is only meaningful if the same envelope decodes to the same bytes every
|
||
/// time. Decode twice through the pinned single-thread runtime and require
|
||
/// bit-identical output.
|
||
#[test]
|
||
fn bmshj2018_decode_is_deterministic() {
|
||
let onnx = model_path("bmshj2018");
|
||
if !onnx.exists() {
|
||
eprintln!("skipping determinism: {} not built", onnx.display());
|
||
return;
|
||
}
|
||
let env = load_envelope("kodim23.bmshj2018.wai");
|
||
let reg = registry();
|
||
let rgb = |w: &Wai| match decode_envelope(w, ®).expect("decode") {
|
||
Decoded::Image(i) => i.rgb,
|
||
_ => panic!("expected image"),
|
||
};
|
||
let first = rgb(&env);
|
||
let second = rgb(&env);
|
||
assert_eq!(first, second, "bmshj2018 decode is not bit-reproducible across runs");
|
||
println!(" bmshj2018: byte-identical across two decodes ({} bytes)", first.len());
|
||
}
|
||
|
||
/// The pin is enforced over the whole parameter set. `wai.neural.bmshj2018` reads
|
||
/// `decoder.onnx` and `cdfs_q<q>.json`, so it is registered with several files: a
|
||
/// form-less pin over the decoder alone is refused (`form_required`) before anything
|
||
/// is read, and a pin over the listing of the set (`wai.prior.set`) decodes; a pin
|
||
/// over another listing is refused (`prior_not_held`), never a silent wrong image.
|
||
#[test]
|
||
fn pinned_prior_is_enforced() {
|
||
let onnx = model_path("bmshj2018");
|
||
if !onnx.exists() {
|
||
eprintln!("skipping prior-pin: {} not built", onnx.display());
|
||
return;
|
||
}
|
||
let base = load_envelope("kodim23.bmshj2018.wai");
|
||
let pin = |p: Prior| -> Wai {
|
||
let mut m = base.manifest.clone();
|
||
m.conditioning.prior = Some(p);
|
||
Wai::new(m, base.payload.clone())
|
||
};
|
||
let det = "int-rans/p16+onnx-cpu-f32/1thread";
|
||
// Form-less over a several-file capability: refused before anything is read.
|
||
let decoder_only = Prior::new("bmshj2018-factorized/q3", model_sha256(&onnx).unwrap(), det);
|
||
assert_eq!(refusal(decode_envelope(&pin(decoder_only), ®istry())), "form_required");
|
||
|
||
// The listing of decoder.onnx and every cdfs_q*.json, in a scratch copy of the set.
|
||
let dir = std::env::temp_dir().join(format!("wai-bmshj-set-{}", std::process::id()));
|
||
std::fs::create_dir_all(&dir).unwrap();
|
||
let mut names = vec!["decoder.onnx".to_string()];
|
||
for e in std::fs::read_dir(onnx.parent().unwrap()).unwrap().flatten() {
|
||
let n = e.file_name().to_string_lossy().into_owned();
|
||
if n.starts_with("cdfs_q") && n.ends_with(".json") {
|
||
names.push(n);
|
||
}
|
||
}
|
||
for n in &names {
|
||
std::fs::copy(onnx.parent().unwrap().join(n), dir.join(n)).unwrap();
|
||
}
|
||
let listing = wai::pin::write_listing(&dir, &names).unwrap();
|
||
std::fs::write(dir.join("set.sha256"), &listing).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.bmshj2018", dir.join("decoder.onnx"));
|
||
let set_pin = Prior::new("bmshj2018-factorized/q3", wai::pin::sha256_hex(&listing), det)
|
||
.with_form(wai::codecs::CAP_PRIOR_SET);
|
||
assert!(matches!(decode_envelope(&pin(set_pin.clone()), ®), Ok(Decoded::Image(_))),
|
||
"the set pin over decoder.onnx and its CDF tables decodes");
|
||
let mut other = set_pin;
|
||
other.sha256 = "00".repeat(32);
|
||
assert_eq!(refusal(decode_envelope(&pin(other), ®)), "prior_not_held");
|
||
std::fs::remove_dir_all(&dir).ok();
|
||
println!(" pin: form-less refused (form_required), set pin decodes, other set refused");
|
||
}
|
||
|
||
/// The pin check is pure hashing and runs with no ONNX model present, so this
|
||
/// exercises SPEC §3.1's verification on every machine. A stand-in file plays the
|
||
/// one-file parameter set of `wai.neural.encodec32`; the registry must accept the
|
||
/// true digest and an absent pin, and refuse every other pin with its reason.
|
||
#[test]
|
||
fn resolve_checked_verifies_prior_hash() {
|
||
let path = std::env::temp_dir().join(format!("wai_prior_check_model_{}.bin", std::process::id()));
|
||
std::fs::write(&path, b"pretend onnx decoder weights v1").unwrap();
|
||
let real = model_sha256(&path).expect("hash stand-in model");
|
||
const ENCODEC: &str = "wai.neural.encodec32";
|
||
let reg = ModelRegistry::new()
|
||
.register(ENCODEC, path.clone())
|
||
.register("wai.neural.bmshj2018", path.clone());
|
||
let code = |cap: &str, p: Option<&Prior>| match reg.resolve_verified(cap, p) {
|
||
Ok(_) => "ok".to_string(),
|
||
Err(DecodeError::PriorMismatch(r)) => r.code().to_string(),
|
||
Err(e) => format!("{e:?}"),
|
||
};
|
||
|
||
// No pin → unverified pass (the honest meaning of an unpinned decode).
|
||
assert_eq!(code(ENCODEC, None), "ok");
|
||
// The true digest → pass; the verified bytes are the file's.
|
||
let good = Prior::new("m/q3", real.clone(), "x");
|
||
assert_eq!(code(ENCODEC, Some(&good)), "ok");
|
||
// Another digest → refused, loudly.
|
||
assert_eq!(code(ENCODEC, Some(&Prior::new("m/q3", "00".repeat(32), "x"))), "prior_not_held");
|
||
// The true digest uppercased is not 64 lowercase hex: malformed, not compared.
|
||
assert_eq!(code(ENCODEC, Some(&Prior::new("m/q3", real.to_ascii_uppercase(), "x"))), "pin_malformed");
|
||
// A form this sink cannot read → refused as unreadable, not as a mismatch.
|
||
match reg.resolve_verified(ENCODEC, Some(&good.clone().with_form(wai::codecs::CAP_PRIOR_NNC))) {
|
||
Err(DecodeError::PriorMismatch(why)) => {
|
||
assert_eq!(why.code(), "form_unreadable");
|
||
assert!(why.to_string().contains("wai.prior.nnc") && why.to_string().contains("cannot be read by this sink"), "{why}");
|
||
}
|
||
other => panic!("an unreadable declared form must be refused, got {other:?}"),
|
||
}
|
||
// A declared form that is not a registered prior form.
|
||
match reg.resolve_verified(ENCODEC, Some(&good.clone().with_form("wai.image.png"))) {
|
||
Err(DecodeError::PriorMismatch(why)) => {
|
||
assert_eq!(why.code(), "form_not_registered");
|
||
assert!(why.to_string().contains("is not a registered prior form"), "{why}");
|
||
}
|
||
other => panic!("a non-form declared as a form must be refused, got {other:?}"),
|
||
}
|
||
// The same stand-in under a several-file capability: form-less is refused.
|
||
assert_eq!(code("wai.neural.bmshj2018", Some(&good)), "form_required");
|
||
// Unknown capability → unregistered, regardless of pin.
|
||
assert!(matches!(reg.resolve_verified("wai.neural.nope", None), Err(DecodeError::ModelNotRegistered(_))));
|
||
|
||
let _ = std::fs::remove_file(&path);
|
||
println!(" resolve_verified: none=ok, true=ok, wrong/upper/forms/several-files refused with their reasons");
|
||
}
|
||
|
||
#[test]
|
||
fn video_bmshj2018_decodes_natively() {
|
||
let onnx = model_path("bmshj2018");
|
||
if !onnx.exists() {
|
||
eprintln!("skipping video_bmshj2018: {} not built", onnx.display());
|
||
return;
|
||
}
|
||
let env = load_envelope("test.video.wai");
|
||
let dec = decode_envelope(&env, ®istry()).expect("video decode");
|
||
match dec {
|
||
Decoded::Video(v) => {
|
||
assert!(v.frames_rgb.len() > 0, "no frames decoded");
|
||
assert_eq!(v.width, 256);
|
||
assert_eq!(v.height, 256);
|
||
assert!((v.fps - 12.0).abs() < 0.01, "fps {} != 12", v.fps);
|
||
// First frame should have non-trivial variance.
|
||
let f0 = &v.frames_rgb[0];
|
||
let mean = f0.iter().map(|&v| v as f64).sum::<f64>() / f0.len() as f64;
|
||
let var = f0.iter().map(|&v| (v as f64 - mean).powi(2)).sum::<f64>() / f0.len() as f64;
|
||
assert!(var > 100.0, "video frame 0 variance {var:.0} suspiciously low");
|
||
println!(" video_bmshj2018: {} frames {}x{} @ {} fps",
|
||
v.frames_rgb.len(), v.width, v.height, v.fps);
|
||
}
|
||
_ => panic!("video_bmshj2018: expected Decoded::Video"),
|
||
}
|
||
}
|
||
|
||
/// The replicate-soundness gate runs BEFORE any model is touched, so this is
|
||
/// model-independent. `replicate` promises decode-equivalence (SPEC §7), so the
|
||
/// capability's registry class decides first: only an integer-exact capability
|
||
/// can back it, and then only with a pinned integer-exact prior. A prior that
|
||
/// *declares* an integer contract over a float codec or over JPEG AI (integer
|
||
/// entropy decode, float later stages) is refused. Non-`replicate` intents are
|
||
/// unconstrained.
|
||
#[test]
|
||
fn strict_decode_gates_unsound_replicate() {
|
||
fn env(cap: &str, prior: Option<Prior>, intent: &str) -> Wai {
|
||
let mut conditioning = Conditioning::new("k");
|
||
conditioning.prior = prior;
|
||
let manifest = Manifest::new("image", intent, ModelRequirement::new(cap), conditioning)
|
||
.with_target(serde_json::json!({"w": 256, "h": 256}));
|
||
Wai::new(manifest, vec![])
|
||
}
|
||
let p = |det: &str| Prior::new("m", "ab".repeat(32), det);
|
||
// An integer-exact network read out of a form not established exact: refused
|
||
// before any model is touched, and the reason names the form. The envelope is
|
||
// built here, over an integer-exact capability, so neither the capability nor
|
||
// the contract can be what refuses it; the same prior without a form passes.
|
||
{
|
||
let over_int_synth = |prior: Prior| {
|
||
Wai::new(
|
||
Manifest::new("image", "replicate", ModelRequirement::new("wai.neural.int_synth"),
|
||
Conditioning::new("int_synth").with_prior(prior))
|
||
.with_target(serde_json::json!({"w": 256, "h": 256})),
|
||
vec![],
|
||
)
|
||
};
|
||
let exact = p("int-rans/p16+onnx-int8-integerized");
|
||
let nnc = exact.clone().with_form(wai::codecs::CAP_PRIOR_NNC);
|
||
let reg = ModelRegistry::new();
|
||
assert!(matches!(
|
||
decode_envelope_strict(&over_int_synth(exact), ®),
|
||
Err(DecodeError::ModelNotRegistered(_))
|
||
));
|
||
match decode_envelope_strict(&over_int_synth(nnc), ®) {
|
||
Err(DecodeError::UnsoundReplicate(why)) => {
|
||
assert!(why.contains("wai.prior.nnc"), "the refusal must name the form: {why}");
|
||
assert!(!why.contains("not integer-exact"), "the contract is integer-exact: {why}");
|
||
}
|
||
Err(e) => panic!("expected UnsoundReplicate, got {e:?}"),
|
||
Ok(_) => panic!("expected UnsoundReplicate, got a decode"),
|
||
}
|
||
}
|
||
let integer = p("int-rans/p16+onnx-int8-integerized");
|
||
let floaty = p("int-rans/p16+onnx-cpu-f32/1thread");
|
||
const FLOAT_CODEC: &str = "wai.neural.bmshj2018";
|
||
const INT_CODEC: &str = "wai.neural.int_synth";
|
||
const JPEGAI: &str = "wai.image.jpegai";
|
||
|
||
let reg = ModelRegistry::new(); // empty: the gate fires before model resolution
|
||
|
||
// replicate over a float codec → rejected, whatever the prior declares.
|
||
for prior in [Some(floaty.clone()), Some(integer.clone()), None] {
|
||
assert!(matches!(
|
||
decode_envelope_strict(&env(FLOAT_CODEC, prior, "replicate"), ®),
|
||
Err(DecodeError::UnsoundReplicate(_))
|
||
));
|
||
}
|
||
// replicate over JPEG AI → rejected even with an integer-declared prior: its
|
||
// registry class is entropy-consistency only.
|
||
match decode_envelope_strict(&env(JPEGAI, Some(integer.clone()), "replicate"), ®) {
|
||
Err(DecodeError::UnsoundReplicate(why)) => assert!(why.contains("entropy-consistency"), "{why}"),
|
||
other => panic!("jpegai replicate must be refused, got {:?}", other.map(|_| ())),
|
||
}
|
||
// replicate over the integer-exact codec: float or absent prior → rejected …
|
||
for prior in [Some(floaty.clone()), None] {
|
||
assert!(matches!(
|
||
decode_envelope_strict(&env(INT_CODEC, prior, "replicate"), ®),
|
||
Err(DecodeError::UnsoundReplicate(_))
|
||
));
|
||
}
|
||
// … integer-exact prior → gate PASSES, falls through to model resolution
|
||
// (ModelNotRegistered proves the gate let it past).
|
||
assert!(matches!(
|
||
decode_envelope_strict(&env(INT_CODEC, Some(integer.clone()), "replicate"), ®),
|
||
Err(DecodeError::ModelNotRegistered(_))
|
||
));
|
||
// Non-replicate intents aren't constrained by the gate.
|
||
for cap in [FLOAT_CODEC, JPEGAI] {
|
||
assert!(matches!(
|
||
decode_envelope_strict(&env(cap, Some(floaty.clone()), "improve"), ®),
|
||
Err(DecodeError::ModelNotRegistered(_))
|
||
));
|
||
}
|
||
// An unregistered capability is reported as such.
|
||
assert!(matches!(
|
||
decode_envelope_strict(&env("wai.image.nope", Some(integer), "replicate"), ®),
|
||
Err(DecodeError::UnknownCapability(_))
|
||
));
|
||
println!(" strict gate: class first (float, entropy-only refused), then prior; non-replicate passes");
|
||
}
|
||
|
||
/// `wai.neural.int_mlicpp` through the neural dispatch and the strict `replicate`
|
||
/// gate, on the committed conformance vector (`tests/vectors/int_mlicpp/`, no model
|
||
/// to fetch, so this never skips). The registered model is the one-file bundle the
|
||
/// envelope's prior pins: the decode must reproduce the pinned image, a bundle that
|
||
/// differs by one byte must be refused before anything is decoded, and `replicate`
|
||
/// with the prior removed must be refused by the gate. The vector's weights are
|
||
/// synthetic, so this checks the dispatch and the pin, not compression quality.
|
||
#[test]
|
||
fn int_mlicpp_decodes_through_the_strict_dispatch() {
|
||
let d = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_mlicpp");
|
||
let bundle = d.join("model.wmb");
|
||
let env = Wai::from_bytes(&std::fs::read(d.join("stream.wai")).unwrap()).unwrap();
|
||
let expected: serde_json::Value = serde_json::from_slice(&std::fs::read(d.join("expected.json")).unwrap()).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.int_mlicpp", &bundle);
|
||
assert_eq!(model_sha256(&bundle).unwrap(), env.prior().expect("pinned prior").sha256);
|
||
match decode_envelope_strict(&env, ®) {
|
||
Ok(Decoded::Image(img)) => {
|
||
assert_eq!((img.width, img.height), (64, 64));
|
||
assert_eq!(format!("{:016x}", wai::int_hyper_synth::fnv1a(&img.rgb)),
|
||
expected["rgb_fnv1a"].as_str().unwrap(), "the pinned image");
|
||
}
|
||
Ok(_) => panic!("int_mlicpp must decode to an image"),
|
||
Err(e) => panic!("int_mlicpp strict decode failed: {e}"),
|
||
}
|
||
|
||
let tampered = std::env::temp_dir().join(format!("wai-int-mlicpp-{}.wmb", std::process::id()));
|
||
let mut bytes = std::fs::read(&bundle).unwrap();
|
||
let at = bytes.len() - 2;
|
||
bytes[at] ^= 1;
|
||
std::fs::write(&tampered, &bytes).unwrap();
|
||
let refused = decode_envelope_strict(&env, &ModelRegistry::new().register("wai.neural.int_mlicpp", &tampered));
|
||
std::fs::remove_file(&tampered).ok();
|
||
assert!(matches!(refused, Err(DecodeError::PriorMismatch(_))), "a one-byte-different bundle must be refused");
|
||
|
||
let mut manifest = env.manifest.clone();
|
||
manifest.conditioning.prior = None;
|
||
let unpinned = Wai::new(manifest, env.payload.clone());
|
||
assert!(matches!(decode_envelope_strict(&unpinned, ®), Err(DecodeError::UnsoundReplicate(_))),
|
||
"replicate without a pinned prior must be refused");
|
||
println!(" int_mlicpp: strict dispatch decodes the pinned image; tampered bundle and unpinned replicate refused");
|
||
}
|
||
|
||
fn int_mlicpp_dir() -> PathBuf {
|
||
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_mlicpp")
|
||
}
|
||
|
||
fn image_fnv(r: Result<Decoded, DecodeError>) -> String {
|
||
match r {
|
||
Ok(Decoded::Image(img)) => format!("{:016x}", wai::int_hyper_synth::fnv1a(&img.rgb)),
|
||
Ok(_) => "not an image".into(),
|
||
Err(e) => format!("error: {e}"),
|
||
}
|
||
}
|
||
|
||
/// `stream_set.wai` pins the same bundle through its listing (`wai.prior.set`,
|
||
/// `decode-equivalence`). With `model.wmb` registered, the registry finds the
|
||
/// listing beside it, verifies it and the bundle, and the strict dispatch decodes the
|
||
/// pinned image from the verified bytes.
|
||
#[test]
|
||
fn int_mlicpp_set_pin_through_the_strict_dispatch() {
|
||
let d = int_mlicpp_dir();
|
||
let env = Wai::from_bytes(&std::fs::read(d.join("stream_set.wai")).unwrap()).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.int_mlicpp", d.join("model.wmb"));
|
||
assert_eq!(image_fnv(decode_envelope_strict(&env, ®)), "7c0b9a4b3423d775");
|
||
}
|
||
|
||
/// SPEC §3.1 check 5 at the neural sink: a registry given a key log that holds
|
||
/// `wai.neural.int_mlicpp`'s pinned sets to jwp-receipts §7.9 refuses the committed set
|
||
/// once a key log revocation names its digest (`prior_revoked`), refuses it offline
|
||
/// (`prior_not_authorised`), and decodes it while a current grant holds it. A key log
|
||
/// that does not hold the capability to §7.9 still refuses the revoked set. The same
|
||
/// envelope with its pin removed decodes the registered set unpinned, and check 5 runs
|
||
/// over that set too (SPEC §4, *Module authorisation*).
|
||
#[cfg(feature = "key_discovery")]
|
||
#[test]
|
||
fn int_mlicpp_revoked_set_is_refused_through_the_key_log() {
|
||
use ed25519_dalek::SigningKey;
|
||
use wai::key_log::{commitment, Anchor, KeyEntry, KeyStatus, ModuleForm, ModuleGrant, Revision, SinkKeyLog};
|
||
const P: &str = "https://publisher.example";
|
||
const MLIC: &str = "wai.neural.int_mlicpp";
|
||
let key = |s: u8| SigningKey::from_bytes(&[s; 32]);
|
||
let d = int_mlicpp_dir();
|
||
let env = Wai::from_bytes(&std::fs::read(d.join("stream_set.wai")).unwrap()).unwrap();
|
||
let digest = wai::pin::sha256(&std::fs::read(d.join("model.sha256")).unwrap());
|
||
let sink_held = |revoked: Vec<[u8; 32]>, statuses: bool, held_to: bool| {
|
||
let unsigned = Revision {
|
||
origin: P.into(),
|
||
seq: 0,
|
||
prev: None,
|
||
issued_at: "2026-09-01T00:00:00Z".into(),
|
||
root: [0; 32],
|
||
next_root: commitment(&key(0x62).verifying_key().to_bytes()),
|
||
keys: vec![KeyEntry::active("modd", key(0x6b).verifying_key().to_bytes(), &["module/decoder"])],
|
||
revoked_modules: vec![],
|
||
grant_max_s: 3_600,
|
||
status_max_age_s: 600,
|
||
sig: [0; 64],
|
||
};
|
||
let r0 = Revision::seal(&key(0x61), unsigned.clone()).unwrap();
|
||
let r1 = Revision::seal(
|
||
&key(0x61),
|
||
Revision { seq: 1, prev: Some(r0.revision_hash()), issued_at: "2026-09-10T00:00:00Z".into(), revoked_modules: revoked, ..unsigned },
|
||
)
|
||
.unwrap();
|
||
let grant = ModuleGrant::seal(&key(0x6b), "did:web:publisher.example#modd", P, digest, ModuleForm::Decoder, &[MLIC],
|
||
"2026-09-20T05:30:00Z", "2026-09-20T06:30:00Z").unwrap().to_json();
|
||
let st = if statuses { vec![KeyStatus::seal(&key(0x61), &r1, "2026-09-20T05:55:00Z").to_json()] } else { vec![] };
|
||
let k = SinkKeyLog::new(vec![r0.clone(), r1], vec![Anchor::pinned(P, r0.revision_hash())])
|
||
.with_statuses(st)
|
||
.with_grants(vec![grant])
|
||
.at("2026-09-20T06:00:00Z");
|
||
if held_to { k.hold_pinned_sets(MLIC, &[P]) } else { k }
|
||
};
|
||
let sink = |revoked: Vec<[u8; 32]>, statuses: bool| sink_held(revoked, statuses, true);
|
||
let reg = |k: SinkKeyLog| ModelRegistry::new().register(MLIC, d.join("model.wmb")).with_key_log(k);
|
||
assert_eq!(refusal(decode_envelope(&env, ®(sink(vec![digest], true)))), "prior_revoked");
|
||
assert_eq!(refusal(decode_envelope(&env, ®(sink(vec![], false)))), "prior_not_authorised");
|
||
assert_eq!(image_fnv(decode_envelope_strict(&env, ®(sink(vec![], true)))), "7c0b9a4b3423d775");
|
||
assert_eq!(refusal(decode_envelope(&env, ®(sink_held(vec![digest], true, false)))), "prior_revoked");
|
||
assert_eq!(image_fnv(decode_envelope(&env, ®(sink_held(vec![], false, false)))), "7c0b9a4b3423d775");
|
||
|
||
let mut manifest = env.manifest.clone();
|
||
manifest.conditioning.prior = None;
|
||
let unpinned = Wai::new(manifest, env.payload.clone());
|
||
let plain = ModelRegistry::new().register(MLIC, d.join("model.wmb"));
|
||
assert_eq!(image_fnv(decode_envelope(&unpinned, &plain)), "7c0b9a4b3423d775", "no key log: unpinned decodes");
|
||
assert_eq!(refusal(decode_envelope(&unpinned, ®(sink(vec![digest], true)))), "prior_revoked");
|
||
assert_eq!(refusal(decode_envelope(&unpinned, ®(sink_held(vec![digest], true, false)))), "prior_revoked");
|
||
assert_eq!(refusal(decode_envelope(&unpinned, ®(sink(vec![], false)))), "prior_not_authorised");
|
||
assert_eq!(image_fnv(decode_envelope(&unpinned, ®(sink(vec![], true)))), "7c0b9a4b3423d775");
|
||
assert_eq!(image_fnv(decode_envelope(&unpinned, ®(sink_held(vec![], false, false)))), "7c0b9a4b3423d775");
|
||
// The bundle's own digest, which a form-less pin names, is checked too.
|
||
let bundle = wai::pin::sha256(&std::fs::read(d.join("model.wmb")).unwrap());
|
||
assert_eq!(refusal(decode_envelope(&unpinned, ®(sink_held(vec![bundle], true, false)))), "prior_revoked");
|
||
let refused = reg(sink(vec![digest], true)).resolve_verified(MLIC, None);
|
||
assert!(matches!(refused, Err(DecodeError::PriorMismatch(ref r)) if r.code() == "prior_revoked"), "resolve_verified checks an unpinned set too");
|
||
}
|
||
|
||
/// The int_synth decode reads `int_synth.json` beside the registered path, so that
|
||
/// one file is its parameter set and what a form-less pin covers (SPEC §5). The
|
||
/// gradient corpus vector, pinned by the digest of its spec, decodes to the corpus
|
||
/// hash; a pin over another spec is refused.
|
||
#[test]
|
||
fn int_synth_pin_covers_the_spec_it_reads() {
|
||
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("../neural-conformance/vectors/gradient");
|
||
let spec = std::fs::read(src.join("int_synth.json")).unwrap();
|
||
let digest = wai::pin::sha256_hex(&spec);
|
||
assert_eq!(digest, "1c4705d17c01f865b20d785191c7f63b5c18dcc8fce77e15852cf8240b7368be");
|
||
let latent = std::fs::read(src.join("latent.bin")).unwrap();
|
||
let env = |sha: &str| Wai::new(
|
||
Manifest::new("image", "replicate", ModelRequirement::new("wai.neural.int_synth"),
|
||
Conditioning::new("int_synth").with_prior(Prior::new("gradient", sha, "int-synth/i64"))),
|
||
latent.clone());
|
||
// The registered path is the `model` stub the corpus convention uses.
|
||
let reg = ModelRegistry::new().register("wai.neural.int_synth", src.join("model"));
|
||
match decode_envelope_strict(&env(&digest), ®) {
|
||
Ok(Decoded::Image(img)) => {
|
||
let mut h = blake3::Hasher::new();
|
||
h.update(b"wai:int-synth-rgb\x01");
|
||
h.update(&img.rgb);
|
||
assert_eq!(h.finalize().to_hex().as_str(),
|
||
"1282839b699ad3e79fc810a92ef4220fec7a57a573bb6c844709af4c1fb3f638");
|
||
}
|
||
other => panic!("the spec pin must decode, got {}", image_fnv(other)),
|
||
}
|
||
assert_eq!(refusal(decode_envelope(&env(&"ab".repeat(32)), ®)), "prior_not_held");
|
||
}
|
||
|
||
/// A key log stand-in that revokes the digests it holds.
|
||
#[derive(Debug)]
|
||
struct Revokes(Vec<String>);
|
||
|
||
impl wai::pin::PinAuthority for Revokes {
|
||
fn check(&self, cap: &str, prior: &Prior) -> Result<(), wai::container::PinRefusal> {
|
||
self.check_unpinned(cap, std::slice::from_ref(&prior.sha256))
|
||
}
|
||
fn check_unpinned(&self, cap: &str, digests: &[String]) -> Result<(), wai::container::PinRefusal> {
|
||
if digests.iter().any(|d| self.0.contains(d)) {
|
||
return Err(wai::container::PinRefusal::Revoked { capability: cap.into(), revoked_by: None });
|
||
}
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
/// `wai.neural.int_synth` registered as a model directory, or as the `int_synth.json`
|
||
/// inside it: the decode reads that one file, a form-less pin covers it, and check 5
|
||
/// runs over it, so a key log that revokes the spec refuses the unpinned decode either
|
||
/// way (`prior_revoked`). Without the key log both registrations decode to the corpus
|
||
/// hash. The unpinned decode reads the bytes check 5 ran over: the spec replaced on
|
||
/// disk after the registry read it does not change the image.
|
||
#[test]
|
||
fn int_synth_check_5_covers_the_spec_a_model_directory_holds() {
|
||
let cap = "wai.neural.int_synth";
|
||
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("../neural-conformance/vectors/gradient");
|
||
let spec = std::fs::read(src.join("int_synth.json")).unwrap();
|
||
let latent = std::fs::read(src.join("latent.bin")).unwrap();
|
||
let digest = wai::pin::sha256_hex(&spec);
|
||
let dir = std::env::temp_dir().join(format!("wai-int-synth-dir-{}", std::process::id()));
|
||
std::fs::create_dir_all(&dir).unwrap();
|
||
std::fs::write(dir.join("int_synth.json"), &spec).unwrap();
|
||
let env = |prior: Option<Prior>| {
|
||
let mut cond = Conditioning::new("int_synth");
|
||
if let Some(p) = prior {
|
||
cond = cond.with_prior(p);
|
||
}
|
||
Wai::new(Manifest::new("image", "improve", ModelRequirement::new(cap), cond), latent.clone())
|
||
};
|
||
let rgb_hash = |r: Result<Decoded, DecodeError>| match r {
|
||
Ok(Decoded::Image(img)) => {
|
||
let mut h = blake3::Hasher::new();
|
||
h.update(b"wai:int-synth-rgb\x01");
|
||
h.update(&img.rgb);
|
||
h.finalize().to_hex().to_string()
|
||
}
|
||
other => refusal(other),
|
||
};
|
||
let want = "1282839b699ad3e79fc810a92ef4220fec7a57a573bb6c844709af4c1fb3f638";
|
||
for path in [dir.clone(), dir.join("int_synth.json")] {
|
||
let revoking = ModelRegistry::new().register(cap, path.clone()).with_key_log(Revokes(vec![digest.clone()]));
|
||
assert_eq!(refusal(decode_envelope(&env(None), &revoking)), "prior_revoked", "{}", path.display());
|
||
let plain = ModelRegistry::new().register(cap, path.clone());
|
||
assert_eq!(rgb_hash(decode_envelope(&env(None), &plain)), want, "{}", path.display());
|
||
let pin = Prior::new("gradient", digest.clone(), "int-synth/i64");
|
||
assert_eq!(rgb_hash(decode_envelope(&env(Some(pin)), &plain)), want, "{}: a form-less pin", path.display());
|
||
}
|
||
let reg = ModelRegistry::new().register(cap, dir.clone()).with_key_log(Revokes(vec![]));
|
||
let params = reg.resolve_verified(cap, None).expect("the unrevoked spec resolves");
|
||
std::fs::copy(src.join("../bloom/int_synth.json"), dir.join("int_synth.json")).unwrap();
|
||
assert_eq!(rgb_hash(wai::neural::decode_payload(cap, &latent, ¶ms)), want, "the decode read what check 5 ran over");
|
||
std::fs::remove_dir_all(&dir).ok();
|
||
}
|
||
|
||
/// For every several-file capability the reference sink dispatches, a form-less pin
|
||
/// is refused with `form_required` before any file is read — even when its digest is
|
||
/// the stand-in's own, and even for a capability registered as a directory.
|
||
#[test]
|
||
fn several_file_capabilities_need_the_set_form() {
|
||
let stand_in = std::env::temp_dir().join(format!("wai-several-{}.bin", std::process::id()));
|
||
std::fs::write(&stand_in, b"stand-in").unwrap();
|
||
let digest = model_sha256(&stand_in).unwrap();
|
||
let mut n = 0;
|
||
for &cap in wai::codecs::known_capabilities() {
|
||
if wai::codecs::parameter_set_shape(cap) != Some(wai::codecs::ParameterSetShape::SeveralFiles) {
|
||
continue;
|
||
}
|
||
let path = if cap == "wai.image.jpegai" { std::env::temp_dir() } else { stand_in.clone() };
|
||
let reg = ModelRegistry::new().register(cap, path);
|
||
let env = Wai::new(
|
||
Manifest::new("image", "improve", ModelRequirement::new(cap),
|
||
Conditioning::new("k").with_prior(Prior::new("m", digest.clone(), "x"))),
|
||
vec![]);
|
||
assert_eq!(refusal(decode_envelope(&env, ®)), "form_required", "{cap}");
|
||
n += 1;
|
||
}
|
||
assert!(n > 0);
|
||
std::fs::remove_file(&stand_in).ok();
|
||
}
|
||
|
||
/// A pinned decode reads its parameters only from the bytes it verified: the spec
|
||
/// file is overwritten after verification, and the decode still gives the image of
|
||
/// the verified spec.
|
||
#[test]
|
||
fn pinned_decode_reads_only_verified_bytes() {
|
||
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("../neural-conformance/vectors/gradient");
|
||
let dir = std::env::temp_dir().join(format!("wai-verified-bytes-{}", std::process::id()));
|
||
std::fs::create_dir_all(&dir).unwrap();
|
||
std::fs::copy(src.join("int_synth.json"), dir.join("int_synth.json")).unwrap();
|
||
let spec = std::fs::read(dir.join("int_synth.json")).unwrap();
|
||
let latent = std::fs::read(src.join("latent.bin")).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.int_synth", dir.join("model"));
|
||
let pin = Prior::new("gradient", wai::pin::sha256_hex(&spec), "int-synth/i64");
|
||
let params = reg.resolve_verified("wai.neural.int_synth", Some(&pin)).expect("verified");
|
||
let before = wai::neural::decode_payload("wai.neural.int_synth", &latent, ¶ms);
|
||
// Replace the spec on disk with another one (the bloom vector's).
|
||
std::fs::copy(src.join("../bloom/int_synth.json"), dir.join("int_synth.json")).unwrap();
|
||
let after = wai::neural::decode_payload("wai.neural.int_synth", &latent, ¶ms);
|
||
assert_eq!(image_fnv(before), image_fnv(after), "the decode read the bytes it verified");
|
||
// An unpinned decode reads the file as it is now, and so differs (or fails).
|
||
let unpinned = wai::neural::decode_payload("wai.neural.int_synth", &latent,
|
||
®.resolve_verified("wai.neural.int_synth", None).unwrap());
|
||
assert_ne!(image_fnv(unpinned), image_fnv(wai::neural::decode_payload("wai.neural.int_synth", &latent, ¶ms)));
|
||
std::fs::remove_dir_all(&dir).ok();
|
||
}
|
||
|
||
/// SPEC §7: the replicate rule reads the capability the envelope decodes through.
|
||
/// The primary, `wai.image.jpegai`, cannot back `replicate`; the fallback,
|
||
/// `wai.neural.int_mlicpp`, can, and the pin is the committed bundle's. With only
|
||
/// the fallback held, and with both held (the primary's form-less pin is then
|
||
/// refused, `form_required`), the strict decode goes through the fallback and gives
|
||
/// the pinned image. With only the primary held it is refused before any file is read.
|
||
#[test]
|
||
fn strict_gate_reads_the_capability_it_decodes_through() {
|
||
let d = int_mlicpp_dir();
|
||
let committed = Wai::from_bytes(&std::fs::read(d.join("stream.wai")).unwrap()).unwrap();
|
||
let mut manifest = committed.manifest.clone();
|
||
manifest.model_requirement = ModelRequirement::new("wai.image.jpegai").with_fallback("wai.neural.int_mlicpp");
|
||
let env = Wai::new(manifest, committed.payload.clone());
|
||
assert_eq!(env.intent(), "replicate");
|
||
let only_fallback = ModelRegistry::new().register("wai.neural.int_mlicpp", d.join("model.wmb"));
|
||
assert_eq!(image_fnv(decode_envelope_strict(&env, &only_fallback)), "7c0b9a4b3423d775");
|
||
let both = ModelRegistry::new()
|
||
.register("wai.image.jpegai", std::env::temp_dir())
|
||
.register("wai.neural.int_mlicpp", d.join("model.wmb"));
|
||
assert_eq!(image_fnv(decode_envelope_strict(&env, &both)), "7c0b9a4b3423d775");
|
||
let only_primary = ModelRegistry::new().register("wai.image.jpegai", std::env::temp_dir());
|
||
assert!(matches!(decode_envelope_strict(&env, &only_primary), Err(DecodeError::UnsoundReplicate(_))));
|
||
}
|
||
|
||
/// SPEC §4 steps 3 and 5 take only a dispatchable capability, so a string the
|
||
/// sink's registry happens to hold but no dispatch could take (a record, a
|
||
/// candidate) is not one the strict decode could go through: the strict path's
|
||
/// early refusal does not read it, and the envelope gets the reason it gets from a
|
||
/// registry that holds nothing (step 6's missing model), the same reason the
|
||
/// non-strict path gives. A record has no replicate class, so a gate that read it
|
||
/// would refuse the envelope as naming an unknown capability.
|
||
#[test]
|
||
fn strict_gate_ignores_a_held_capability_it_cannot_dispatch() {
|
||
use wai::codecs::{capability_kind, is_candidate, registry::CAP_VIDEO_MANIFEST, Kind, CAP_TENSOR_NNC};
|
||
let d = int_mlicpp_dir();
|
||
let committed = Wai::from_bytes(&std::fs::read(d.join("stream.wai")).unwrap()).unwrap();
|
||
assert_eq!(capability_kind(CAP_VIDEO_MANIFEST), Some(Kind::Record));
|
||
assert!(is_candidate(CAP_TENSOR_NNC));
|
||
for held in [CAP_VIDEO_MANIFEST, CAP_TENSOR_NNC] {
|
||
let mut manifest = committed.manifest.clone();
|
||
manifest.model_requirement = ModelRequirement::new("wai.neural.int_mlicpp").with_fallback(held);
|
||
let env = Wai::new(manifest, committed.payload.clone());
|
||
assert_eq!(env.intent(), "replicate");
|
||
let holds = ModelRegistry::new().register(held, std::env::temp_dir());
|
||
for (path, got) in [
|
||
("strict, held", decode_envelope_strict(&env, &holds)),
|
||
("strict, empty registry", decode_envelope_strict(&env, &ModelRegistry::new())),
|
||
("non-strict, held", decode_envelope(&env, &holds)),
|
||
] {
|
||
assert!(matches!(got, Err(DecodeError::ModelNotRegistered(_))),
|
||
"{held}, {path}: expected ModelNotRegistered, got {:?}", got.map(|_| ()));
|
||
}
|
||
}
|
||
}
|
||
|
||
/// SPEC §4 step 6: when the capability's pinned decode is refused with
|
||
/// `set_incomplete` (step 3) and the fallback's pin is then refused (step 5), the
|
||
/// error is step 3's reason. The pin is a `wai.prior.set` listing whose one file is
|
||
/// not the `int_synth.json` the int_synth decode reads; the fallback holds another set.
|
||
#[test]
|
||
fn set_incomplete_at_step_3_outranks_the_fallback_refusal() {
|
||
let dir = std::env::temp_dir().join(format!("wai-step6-{}", std::process::id()));
|
||
std::fs::create_dir_all(&dir).unwrap();
|
||
std::fs::write(dir.join("other.bin"), b"not the spec").unwrap();
|
||
let listing = format!("{} other.bin\n", wai::pin::sha256_hex(b"not the spec"));
|
||
std::fs::write(dir.join("set.sha256"), &listing).unwrap();
|
||
let pin = Prior::new("set", wai::pin::sha256_hex(listing.as_bytes()), "int-synth/i64")
|
||
.with_form(wai::codecs::CAP_PRIOR_SET);
|
||
let latent = std::fs::read(Path::new(env!("CARGO_MANIFEST_DIR"))
|
||
.join("../neural-conformance/vectors/gradient/latent.bin")).unwrap();
|
||
let env = Wai::new(
|
||
Manifest::new("image", "improve",
|
||
ModelRequirement::new("wai.neural.int_synth").with_fallback("wai.neural.int_mlicpp"),
|
||
Conditioning::new("int_synth").with_prior(pin)),
|
||
latent);
|
||
let reg = ModelRegistry::new()
|
||
.register("wai.neural.int_synth", dir.join("model"))
|
||
.register("wai.neural.int_mlicpp", int_mlicpp_dir().join("model.wmb"));
|
||
let got = refusal(decode_envelope(&env, ®));
|
||
std::fs::remove_dir_all(&dir).ok();
|
||
assert_eq!(got, "set_incomplete");
|
||
}
|
||
|
||
/// SPEC §7.1 step 5 under the strict policy: an entry the replicate rule leaves out
|
||
/// is reported with its `replicate_*` reason, not as a capability the sink lacks.
|
||
#[test]
|
||
fn decode_multi_strict_reports_replicate_reasons() {
|
||
use wai::container::{Rendition, WaiMulti};
|
||
use wai::neural::decode_multi_strict;
|
||
let d = int_mlicpp_dir();
|
||
let pl = Wai::from_bytes(&std::fs::read(d.join("stream.wai")).unwrap()).unwrap().payload;
|
||
let m = WaiMulti::new("image", "replicate", serde_json::Value::Null, vec![
|
||
Rendition::media("wai.image.jpegai", "jpegai", vec![0; 4]),
|
||
Rendition::media("wai.neural.int_mlicpp", "int_mlicpp", pl)
|
||
.with_prior(Prior::new("next", "5c".repeat(32), "int-rans/p16+int-mlicpp/i64")),
|
||
]);
|
||
let reg = ModelRegistry::new()
|
||
.register("wai.image.jpegai", std::env::temp_dir())
|
||
.register("wai.neural.int_mlicpp", d.join("model.wmb"));
|
||
match decode_multi_strict(&m, ®) {
|
||
Err(DecodeError::NoRendition(r)) => {
|
||
let codes: Vec<(usize, &str)> = r.iter().map(|(i, why)| (*i, why.split(':').next().unwrap())).collect();
|
||
assert_eq!(codes, [(0, "replicate_class"), (1, "prior_not_held")], "{r:?}");
|
||
}
|
||
other => panic!("expected NoRendition, got {:?}", other.map(|(i, _)| i)),
|
||
}
|
||
}
|
||
|
||
/// SPEC §7.1 through the neural sink: a `WAI2` envelope carrying the committed
|
||
/// int_mlicpp payload twice, pinned to a successor set (which names the committed
|
||
/// bundle in `supersedes`) and to the committed bundle. `decode_multi_strict` skips
|
||
/// the first, picks the second and decodes the pinned image; without the bundle the
|
||
/// envelope is inert, with a reason per entry.
|
||
#[test]
|
||
fn wai2_decode_multi_picks_the_verified_rendition() {
|
||
use wai::codecs::DeclaredTier;
|
||
use wai::container::{Rendition, Supersedes, WaiMulti};
|
||
use wai::neural::{decode_multi, decode_multi_strict};
|
||
let d = int_mlicpp_dir();
|
||
let pl = Wai::from_bytes(&std::fs::read(d.join("stream.wai")).unwrap()).unwrap().payload;
|
||
let committed = model_sha256(&d.join("model.wmb")).unwrap();
|
||
let det = "int-rans/p16+int-mlicpp/i64";
|
||
let m = WaiMulti::new("image", "replicate", serde_json::Value::Null, vec![
|
||
Rendition::media("wai.neural.int_mlicpp", "int_mlicpp", pl.clone())
|
||
.with_prior(Prior::new("next", "5c".repeat(32), det).with_tier(DeclaredTier::DecodeEquivalence)
|
||
.with_supersedes(Supersedes::new(committed.clone()))),
|
||
Rendition::media("wai.neural.int_mlicpp", "int_mlicpp", pl)
|
||
.with_prior(Prior::new("committed", committed, det).with_tier(DeclaredTier::DecodeEquivalence)),
|
||
]);
|
||
let reg = ModelRegistry::new().register("wai.neural.int_mlicpp", d.join("model.wmb"));
|
||
match decode_multi_strict(&m, ®) {
|
||
Ok((i, d)) => {
|
||
assert_eq!(i, 1);
|
||
assert_eq!(image_fnv(Ok(d)), "7c0b9a4b3423d775");
|
||
}
|
||
Err(e) => panic!("decode_multi_strict: {e}"),
|
||
}
|
||
match decode_multi(&m, &ModelRegistry::new()) {
|
||
Err(DecodeError::NoRendition(r)) => assert_eq!(r.len(), 2),
|
||
other => panic!("expected NoRendition, got {:?}", other.map(|(i, _)| i)),
|
||
}
|
||
let tampered = std::env::temp_dir().join(format!("wai-multi-{}.wmb", std::process::id()));
|
||
let mut b = std::fs::read(d.join("model.wmb")).unwrap();
|
||
b[10] ^= 1;
|
||
std::fs::write(&tampered, &b).unwrap();
|
||
match decode_multi(&m, &ModelRegistry::new().register("wai.neural.int_mlicpp", &tampered)) {
|
||
Err(DecodeError::NoRendition(r)) => {
|
||
assert!(r[0].1.starts_with("prior_not_held") && r[1].1.starts_with("prior_not_held"), "{r:?}");
|
||
}
|
||
other => panic!("expected NoRendition, got {:?}", other.map(|(i, _)| i)),
|
||
}
|
||
std::fs::remove_file(&tampered).ok();
|
||
}
|
||
|
||
/// END-TO-END validation against the real CompressAI mbt2018-mean model:
|
||
/// decode the SAME envelope in Rust and compare to CompressAI's own decode.
|
||
/// The Gaussian-conditional entropy decode must be EXACT (same rANS bytes →
|
||
/// same latents); only `g_s` (ONNX vs torch float) may differ by a hair. A
|
||
/// large divergence means the entropy decode is wrong. Artifacts produced by
|
||
/// the export/encode tools + a CompressAI reference decode; skipped if absent.
|
||
#[test]
|
||
fn mbt2018_mean_matches_compressai_reference() {
|
||
let wai = Path::new("/tmp/test.mbt.wai");
|
||
let ref_rgb = Path::new("/tmp/ref.rgb");
|
||
let model_dir = Path::new("/tmp/wai_mbt");
|
||
if !wai.exists() || !ref_rgb.exists() || !model_dir.join("hyper_synthesis.onnx").exists() {
|
||
eprintln!("skipping mbt reference match: /tmp artifacts not present");
|
||
return;
|
||
}
|
||
let env = Wai::read(wai).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.mbt2018_mean", model_dir.join("model"));
|
||
let img = match decode_envelope(&env, ®).expect("rust decode") {
|
||
Decoded::Image(i) => i,
|
||
_ => panic!("expected image"),
|
||
};
|
||
assert_eq!((img.width, img.height), (256, 256));
|
||
let reference = std::fs::read(ref_rgb).unwrap();
|
||
assert_eq!(img.rgb.len(), reference.len());
|
||
let diffs = || img.rgb.iter().zip(&reference).map(|(a, b)| (*a as i32 - *b as i32).abs());
|
||
let maxd = diffs().max().unwrap();
|
||
let meand = diffs().map(|d| d as f64).sum::<f64>() / reference.len() as f64;
|
||
println!(" mbt2018_mean vs CompressAI: max|Δ|={maxd} mean|Δ|={meand:.3}");
|
||
assert!(maxd <= 4, "Rust decode diverges from CompressAI (max {maxd}) — entropy decode wrong");
|
||
}
|
||
|
||
/// END-TO-END validation of the EXISTING factorized `image.rs` decoder against
|
||
/// the real CompressAI bmshj2018-factorized. Factorized = static CDFs (no
|
||
/// scale-from-network), so it shouldn't have the hyperprior's bucket-flip
|
||
/// hazard. Skipped without /tmp artifacts.
|
||
#[test]
|
||
fn bmshj2018_matches_compressai_reference() {
|
||
let wai = Path::new("/tmp/test.bmshj.wai");
|
||
let ref_rgb = Path::new("/tmp/ref_bmshj.rgb");
|
||
let model = Path::new("/tmp/wai_bmshj/decoder.onnx");
|
||
if !wai.exists() || !ref_rgb.exists() || !model.exists() {
|
||
eprintln!("skipping bmshj reference match: /tmp artifacts not present");
|
||
return;
|
||
}
|
||
let env = Wai::read(wai).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.bmshj2018", model);
|
||
let img = match decode_envelope(&env, ®).expect("rust decode") {
|
||
Decoded::Image(i) => i,
|
||
_ => panic!("expected image"),
|
||
};
|
||
let reference = std::fs::read(ref_rgb).unwrap();
|
||
assert_eq!(img.rgb.len(), reference.len());
|
||
let diffs = || img.rgb.iter().zip(&reference).map(|(a, b)| (*a as i32 - *b as i32).abs());
|
||
let maxd = diffs().max().unwrap();
|
||
let meand = diffs().map(|d| d as f64).sum::<f64>() / reference.len() as f64;
|
||
println!(" bmshj2018 vs CompressAI: max|Δ|={maxd} mean|Δ|={meand:.3}");
|
||
assert!(maxd <= 4, "bmshj2018 Rust decode diverges from CompressAI (max {maxd})");
|
||
}
|
||
|
||
/// END-TO-END validation of the checkerboard CONTEXT decoder (`context.rs`)
|
||
/// against a minimal CompressAI-compatible elic model run through the actual
|
||
/// export/encode logic. Validates the 2-pass anchor→context→non-anchor
|
||
/// orchestration + single-stream symbol order. Skipped without /tmp artifacts.
|
||
#[test]
|
||
fn elic_matches_reference() {
|
||
let wai = Path::new("/tmp/test.elic.wai");
|
||
let ref_rgb = Path::new("/tmp/ref_elic.rgb");
|
||
let model = Path::new("/tmp/wai_elic/decoder.onnx");
|
||
let dir = Path::new("/tmp/wai_elic");
|
||
if !wai.exists() || !ref_rgb.exists() || !dir.join("context.onnx").exists() {
|
||
eprintln!("skipping elic reference match: /tmp artifacts not present");
|
||
return;
|
||
}
|
||
let env = Wai::read(wai).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.elic", model);
|
||
let img = match decode_envelope(&env, ®).expect("rust elic decode") {
|
||
Decoded::Image(i) => i,
|
||
_ => panic!("expected image"),
|
||
};
|
||
let reference = std::fs::read(ref_rgb).unwrap();
|
||
assert_eq!(img.rgb.len(), reference.len());
|
||
let diffs = || img.rgb.iter().zip(&reference).map(|(a, b)| (*a as i32 - *b as i32).abs());
|
||
let maxd = diffs().max().unwrap();
|
||
let meand = diffs().map(|d| d as f64).sum::<f64>() / reference.len() as f64;
|
||
println!(" elic vs reference: max|Δ|={maxd} mean|Δ|={meand:.3}");
|
||
assert!(maxd <= 4, "elic Rust decode diverges (max {maxd}) — checkerboard orchestration wrong");
|
||
}
|
||
|
||
/// END-TO-END validation of the MLIC++ channel-autoregressive decoder
|
||
/// (`mlicpp.rs`) against a minimal CompressAI-compatible model: K channel
|
||
/// slices × checkerboard, the entropy net re-run on concat[y_so_far, hyper] as
|
||
/// context grows. Validates the slice-loop ordering. Skipped without artifacts.
|
||
#[test]
|
||
fn mlicpp_matches_reference() {
|
||
let wai = Path::new("/tmp/test.mlic.wai");
|
||
let ref_rgb = Path::new("/tmp/ref_mlic.rgb");
|
||
let model = Path::new("/tmp/wai_mlic/decoder.onnx");
|
||
let dir = Path::new("/tmp/wai_mlic");
|
||
if !wai.exists() || !ref_rgb.exists() || !dir.join("entropy.onnx").exists() {
|
||
eprintln!("skipping mlicpp reference match: /tmp artifacts not present");
|
||
return;
|
||
}
|
||
let env = Wai::read(wai).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.mlicpp", model);
|
||
let img = match decode_envelope(&env, ®).expect("rust mlicpp decode") {
|
||
Decoded::Image(i) => i,
|
||
_ => panic!("expected image"),
|
||
};
|
||
let reference = std::fs::read(ref_rgb).unwrap();
|
||
assert_eq!(img.rgb.len(), reference.len());
|
||
let diffs = || img.rgb.iter().zip(&reference).map(|(a, b)| (*a as i32 - *b as i32).abs());
|
||
let maxd = diffs().max().unwrap();
|
||
let meand = diffs().map(|d| d as f64).sum::<f64>() / reference.len() as f64;
|
||
println!(" mlicpp vs reference: max|Δ|={maxd} mean|Δ|={meand:.3}");
|
||
assert!(maxd <= 4, "mlicpp Rust decode diverges (max {maxd}) — slice orchestration wrong");
|
||
}
|
||
|
||
/// MLICv2 shares MLIC++'s multi-reference
|
||
/// channel-autoregressive decode — its gains live in the sink nets. Prove the
|
||
/// route is LIVE (not just registered): an mlicpp-format envelope relabeled
|
||
/// `wai.neural.mlicv2` decodes identically through `mlicpp::decode`.
|
||
#[test]
|
||
fn mlicv2_routes_through_mlicpp_decoder() {
|
||
let wai = Path::new("/tmp/test.mlic.wai");
|
||
let ref_rgb = Path::new("/tmp/ref_mlic.rgb");
|
||
let model = Path::new("/tmp/wai_mlic/decoder.onnx");
|
||
let dir = Path::new("/tmp/wai_mlic");
|
||
if !wai.exists() || !ref_rgb.exists() || !dir.join("entropy.onnx").exists() {
|
||
eprintln!("skipping mlicv2 route: /tmp artifacts not present");
|
||
return;
|
||
}
|
||
let base = Wai::read(wai).unwrap();
|
||
let mut m = base.manifest.clone();
|
||
m.model_requirement.capability = "wai.neural.mlicv2".into();
|
||
let env = Wai::new(m, base.payload.clone());
|
||
let reg = ModelRegistry::new().register("wai.neural.mlicv2", model);
|
||
let img = match decode_envelope(&env, ®).expect("mlicv2 decode") {
|
||
Decoded::Image(i) => i,
|
||
_ => panic!("expected image"),
|
||
};
|
||
let reference = std::fs::read(ref_rgb).unwrap();
|
||
let maxd = img.rgb.iter().zip(&reference)
|
||
.map(|(a, b)| (*a as i32 - *b as i32).abs()).max().unwrap();
|
||
println!(" mlicv2 (via mlicpp route) vs reference: max|Δ|={maxd}");
|
||
assert!(maxd <= 4, "mlicv2 route diverges (max {maxd}) — dispatch not wired");
|
||
}
|
||
|
||
/// END-TO-END validation of the SNAC multi-resolution decoder (`snac.rs`):
|
||
/// N variable-length code levels → dynamic-input ONNX (codes_0..codes_{N-1}) →
|
||
/// waveform. Validates the multi-level wire parse + dynamic dispatch against a
|
||
/// clean synthetic decoder of SNAC's interface (the real SNAC model has known
|
||
/// ONNX-export issues — noise/attention reshapes — which is a sink-side concern,
|
||
/// not this decoder's). Skipped without /tmp artifacts.
|
||
#[test]
|
||
fn snac_matches_reference() {
|
||
let wai = Path::new("/tmp/test.snac.wai");
|
||
let ref_f = Path::new("/tmp/ref_snac.f32");
|
||
let model = Path::new("/tmp/wai_snac/decoder.onnx");
|
||
if !wai.exists() || !ref_f.exists() || !model.exists() {
|
||
eprintln!("skipping snac reference match: /tmp artifacts not present");
|
||
return;
|
||
}
|
||
let env = Wai::read(wai).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.snac", model);
|
||
let audio = match decode_envelope(&env, ®).expect("snac decode") {
|
||
Decoded::Audio(a) => a,
|
||
_ => panic!("expected audio"),
|
||
};
|
||
assert_eq!(audio.sample_rate, 24_000);
|
||
let ref_bytes = std::fs::read(ref_f).unwrap();
|
||
let reference: Vec<f32> = ref_bytes.chunks_exact(4)
|
||
.map(|c| f32::from_le_bytes(c.try_into().unwrap())).collect();
|
||
assert_eq!(audio.samples.len(), reference.len(), "sample count mismatch");
|
||
let maxd = audio.samples.iter().zip(&reference)
|
||
.map(|(a, b)| (a - b).abs()).fold(0f32, f32::max);
|
||
println!(" snac vs reference: max|Δ|={maxd:.2e} ({} samples, 3 levels)", audio.samples.len());
|
||
assert!(maxd < 1e-4, "snac decode diverges (max {maxd}) — multi-level dispatch wrong");
|
||
}
|
||
|
||
/// CMIC (ICLR 2026, −15.91% vs VTM) uses the SCCTX entropy model (space-channel
|
||
/// context = channel slices + checkerboard) — the mlicpp orchestration; its
|
||
/// Mamba is in the sink transform. Prove the route is live: an mlicpp-format
|
||
/// envelope relabeled `wai.neural.cmic` decodes identically.
|
||
#[test]
|
||
fn cmic_routes_through_mlicpp_decoder() {
|
||
let wai = Path::new("/tmp/test.mlic.wai");
|
||
let ref_rgb = Path::new("/tmp/ref_mlic.rgb");
|
||
let model = Path::new("/tmp/wai_mlic/decoder.onnx");
|
||
let dir = Path::new("/tmp/wai_mlic");
|
||
if !wai.exists() || !ref_rgb.exists() || !dir.join("entropy.onnx").exists() {
|
||
eprintln!("skipping cmic route: /tmp artifacts not present");
|
||
return;
|
||
}
|
||
let base = Wai::read(wai).unwrap();
|
||
let mut m = base.manifest.clone();
|
||
m.model_requirement.capability = "wai.neural.cmic".into();
|
||
let env = Wai::new(m, base.payload.clone());
|
||
let reg = ModelRegistry::new().register("wai.neural.cmic", model);
|
||
let img = match decode_envelope(&env, ®).expect("cmic decode") {
|
||
Decoded::Image(i) => i,
|
||
_ => panic!("expected image"),
|
||
};
|
||
let reference = std::fs::read(ref_rgb).unwrap();
|
||
let maxd = img.rgb.iter().zip(&reference)
|
||
.map(|(a, b)| (*a as i32 - *b as i32).abs()).max().unwrap();
|
||
println!(" cmic (via mlicpp route) vs reference: max|Δ|={maxd}");
|
||
assert!(maxd <= 4, "cmic route diverges (max {maxd}) — dispatch not wired");
|
||
}
|
||
|
||
/// END-TO-END validation of the DCVC INTER-frame video decoder
|
||
/// (`video_dcvc.rs`): a frame loop that propagates a reference feature, decoded
|
||
/// on CPU via ort (no GPU required). 3 moving frames through a
|
||
/// minimal hyperprior + temporal-decoder model; the reference feature threads
|
||
/// frame→frame so frames differ even from similar latents. Skipped w/o artifacts.
|
||
#[test]
|
||
fn dcvc_matches_reference() {
|
||
let wai = Path::new("/tmp/test.dcvc.wai");
|
||
let ref_rgb = Path::new("/tmp/ref_dcvc.rgb");
|
||
let dir = Path::new("/tmp/wai_dcvc");
|
||
if !wai.exists() || !ref_rgb.exists() || !dir.join("temporal_decoder.onnx").exists() {
|
||
eprintln!("skipping dcvc reference match: /tmp artifacts not present");
|
||
return;
|
||
}
|
||
let env = Wai::read(wai).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.dcvc_rt", dir.join("decoder.onnx"));
|
||
let video = match decode_envelope(&env, ®).expect("dcvc decode") {
|
||
Decoded::Video(v) => v,
|
||
_ => panic!("expected video"),
|
||
};
|
||
assert_eq!(video.frames_rgb.len(), 3, "frame count");
|
||
let reference = std::fs::read(ref_rgb).unwrap();
|
||
let flat: Vec<u8> = video.frames_rgb.concat();
|
||
assert_eq!(flat.len(), reference.len(), "total byte count");
|
||
let maxd = flat.iter().zip(&reference)
|
||
.map(|(a, b)| (*a as i32 - *b as i32).abs()).max().unwrap();
|
||
// also confirm frames actually differ (temporal propagation did something)
|
||
let f0_eq_f1 = video.frames_rgb[0] == video.frames_rgb[1];
|
||
println!(" dcvc (3 frames, temporal propagation) vs reference: max|Δ|={maxd} frames-differ={}", !f0_eq_f1);
|
||
assert!(maxd <= 4, "dcvc decode diverges (max {maxd}) — temporal propagation wrong");
|
||
}
|
||
|
||
/// END-TO-END validation of the GLC CATEGORICAL decoder (`glc.rs`): codebook
|
||
/// indices via per-position softmax → CompressAI-exact pmf_to_quantized_cdf →
|
||
/// bypass-free categorical rANS, over a checkerboard. A minimal VQ model + a
|
||
/// matching categorical rANS encoder; reference = synthesis(true indices).
|
||
/// Skipped without /tmp artifacts.
|
||
#[test]
|
||
fn glc_matches_reference() {
|
||
let wai = Path::new("/tmp/test.glc.wai");
|
||
let ref_rgb = Path::new("/tmp/ref_glc.rgb");
|
||
let dir = Path::new("/tmp/wai_glc");
|
||
if !wai.exists() || !ref_rgb.exists() || !dir.join("hyper_entropy.onnx").exists() {
|
||
eprintln!("skipping glc reference match: /tmp artifacts not present");
|
||
return;
|
||
}
|
||
let env = Wai::read(wai).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.glc", dir.join("decoder.onnx"));
|
||
let img = match decode_envelope(&env, ®).expect("glc decode") {
|
||
Decoded::Image(i) => i,
|
||
_ => panic!("expected image"),
|
||
};
|
||
let reference = std::fs::read(ref_rgb).unwrap();
|
||
assert_eq!(img.rgb.len(), reference.len());
|
||
let maxd = img.rgb.iter().zip(&reference)
|
||
.map(|(a, b)| (*a as i32 - *b as i32).abs()).max().unwrap();
|
||
println!(" glc (categorical/VQ, 256 indices) vs reference: max|Δ|={maxd}");
|
||
assert!(maxd <= 4, "glc decode diverges (max {maxd}) — categorical entropy wrong");
|
||
}
|
||
|
||
/// SNAC against the REAL pretrained snac_24khz model (not synthetic): exported
|
||
/// after model surgery — zero-noise (NoiseBlock's torch.randn made decode
|
||
/// stochastic; WAI needs determinism) + an export-clean snake. Validates the
|
||
/// multi-level decoder on the genuine 3-codebook hierarchy. Skipped w/o files.
|
||
#[test]
|
||
fn snac_real_model_matches_reference() {
|
||
let wai = Path::new("/tmp/test.snac_real.wai");
|
||
let ref_f = Path::new("/tmp/ref_snac_real.f32");
|
||
let model = Path::new("/tmp/wai_snac_real/decoder.onnx");
|
||
if !wai.exists() || !ref_f.exists() || !model.exists() {
|
||
eprintln!("skipping real-snac match: /tmp artifacts not present");
|
||
return;
|
||
}
|
||
let env = Wai::read(wai).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.snac", model);
|
||
let audio = match decode_envelope(&env, ®).expect("snac decode") {
|
||
Decoded::Audio(a) => a,
|
||
_ => panic!("expected audio"),
|
||
};
|
||
assert_eq!(audio.sample_rate, 24_000);
|
||
let reference: Vec<f32> = std::fs::read(ref_f).unwrap()
|
||
.chunks_exact(4).map(|c| f32::from_le_bytes(c.try_into().unwrap())).collect();
|
||
assert_eq!(audio.samples.len(), reference.len());
|
||
let maxd = audio.samples.iter().zip(&reference)
|
||
.map(|(a, b)| (a - b).abs()).fold(0f32, f32::max);
|
||
println!(" REAL snac_24khz vs torch(deterministic): max|Δ|={maxd:.2e} ({} samples)", audio.samples.len());
|
||
assert!(maxd < 1e-3, "real snac decode diverges (max {maxd})");
|
||
}
|
||
|
||
/// int_synth against a REAL trained model (not the constructed unit test): a float
|
||
/// autoencoder trained on synthetic images, then quantised to the integer spec with
|
||
/// per-layer activation-calibrated shifts. Confirms the Rust integer decode is
|
||
/// BIT-IDENTICAL to the numpy integer-forward reference on a trained model (the
|
||
/// determinism guarantee, on real weights). Skipped without /tmp artifacts.
|
||
#[test]
|
||
fn int_synth_trained_model_matches_integer_forward() {
|
||
let wai = Path::new("/tmp/test.intsynth.wai");
|
||
let ref_rgb = Path::new("/tmp/ref_intsynth.rgb");
|
||
let dir = Path::new("/tmp/wai_intsynth");
|
||
if !wai.exists() || !ref_rgb.exists() || !dir.join("int_synth.json").exists() {
|
||
eprintln!("skipping trained int_synth: /tmp artifacts not present");
|
||
return;
|
||
}
|
||
let env = Wai::read(wai).unwrap();
|
||
let reg = ModelRegistry::new().register("wai.neural.int_synth", dir.join("model"));
|
||
let img = match decode_envelope(&env, ®).expect("int_synth decode") {
|
||
Decoded::Image(i) => i,
|
||
_ => panic!("expected image"),
|
||
};
|
||
let reference = std::fs::read(ref_rgb).unwrap();
|
||
assert_eq!(img.rgb.len(), reference.len());
|
||
let maxd = img.rgb.iter().zip(&reference)
|
||
.map(|(a, b)| (*a as i32 - *b as i32).abs()).max().unwrap();
|
||
println!(" trained int_synth: Rust vs numpy integer-forward max|Δ|={maxd} (must be 0 — integer-exact)");
|
||
assert_eq!(maxd, 0, "Rust integer decode must be bit-identical to the reference");
|
||
}
|
||
|
||
/// A temporary directory holding the tiny conditional codec's set under its member
|
||
/// names (`g_s.whm`, `h_s.whm`, `entropy.json`) and a listing, `set.sha256`, of the
|
||
/// members `listed`. Returns the directory and the listing's SHA-256.
|
||
fn int_hyper_set_dir(tag: &str, listed: &[&str]) -> (PathBuf, String) {
|
||
let v = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_hyper");
|
||
let dir = std::env::temp_dir().join(format!("wai-int-hyper-{tag}-{}", std::process::id()));
|
||
std::fs::create_dir_all(&dir).unwrap();
|
||
let mut files = std::collections::BTreeMap::new();
|
||
for (member, src) in [("entropy.json", "tiny_entropy.json"), ("g_s.whm", "tiny_g_s.bin"), ("h_s.whm", "tiny_h_s.bin")] {
|
||
let b = std::fs::read(v.join(src)).unwrap();
|
||
std::fs::write(dir.join(member), &b).unwrap();
|
||
if listed.contains(&member) {
|
||
files.insert(member.to_string(), b);
|
||
}
|
||
}
|
||
let view: std::collections::BTreeMap<String, &[u8]> = files.iter().map(|(k, v)| (k.clone(), v.as_slice())).collect();
|
||
let listing = wai::pin::listing_from(&view).unwrap();
|
||
std::fs::write(dir.join("set.sha256"), &listing).unwrap();
|
||
(dir, wai::pin::sha256_hex(&listing))
|
||
}
|
||
|
||
/// `wai.neural.int_hyper` through the neural dispatch, which delegates it to
|
||
/// `int_payload::decode_int`: its `WIH1` payload decodes from the three-file set, pinned
|
||
/// through the set's listing (under the strict `replicate` gate) and unpinned, to the
|
||
/// committed image. A pinned set whose listing leaves out `h_s.whm` is refused with
|
||
/// `set_incomplete` naming it. The tool's JSON output carried as the payload is refused
|
||
/// (`bad_magic`), and the refusal is final: the fallback the envelope names is not tried.
|
||
/// An unpinned set without `h_s.whm` refuses a JSON form as `bad_magic` too, the payload
|
||
/// being checked before any member is read, and a `WIH1` as `set_incomplete`.
|
||
#[test]
|
||
fn int_hyper_decodes_through_the_neural_dispatch() {
|
||
use wai::codecs::{DeclaredTier, CAP_NEURAL_INT_HYPER as HYP, CAP_NEURAL_INT_SYNTH, CAP_PRIOR_SET};
|
||
let v = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_hyper");
|
||
let payload = std::fs::read(v.join("tiny_bitstream.wih1")).unwrap();
|
||
let rgb_sha = "dce7282d46f88f03d5dc0fd42f325756d310fe1fc982b6e1792a1fbfbeb482cf";
|
||
let env = |intent: &str, prior: Option<Prior>, fallback: Option<&str>, payload: Vec<u8>| {
|
||
let mut req = ModelRequirement::new(HYP);
|
||
if let Some(f) = fallback {
|
||
req = req.with_fallback(f);
|
||
}
|
||
let mut cond = Conditioning::new("int_hyper");
|
||
if let Some(p) = prior {
|
||
cond = cond.with_prior(p);
|
||
}
|
||
Wai::new(Manifest::new("image", intent, req, cond), payload)
|
||
};
|
||
let sha = |r: Result<Decoded, DecodeError>| match r {
|
||
Ok(Decoded::Image(img)) => wai::pin::sha256_hex(&img.rgb),
|
||
other => refusal(other),
|
||
};
|
||
let (dir, digest) = int_hyper_set_dir("all", &["entropy.json", "g_s.whm", "h_s.whm"]);
|
||
let reg = ModelRegistry::new().register(HYP, dir.join("g_s.whm"));
|
||
let pin = Prior::new("tiny", digest, "decode-equivalence").with_form(CAP_PRIOR_SET).with_tier(DeclaredTier::DecodeEquivalence);
|
||
assert_eq!(sha(decode_envelope_strict(&env("replicate", Some(pin), None, payload.clone()), ®)), rgb_sha);
|
||
assert_eq!(sha(decode_envelope(&env("improve", None, None, payload.clone()), ®)), rgb_sha);
|
||
|
||
let (part, digest) = int_hyper_set_dir("part", &["entropy.json", "g_s.whm"]);
|
||
let pin = Prior::new("tiny", digest, "decode-equivalence").with_form(CAP_PRIOR_SET);
|
||
let r = decode_envelope(&env("improve", Some(pin), None, payload.clone()), &ModelRegistry::new().register(HYP, part.join("g_s.whm")));
|
||
assert!(matches!(r, Err(DecodeError::PriorMismatch(wai::container::PinRefusal::SetIncomplete { ref path })) if path == "h_s.whm"), "{}", refusal(r));
|
||
|
||
let json = std::fs::read(v.join("tiny_bitstream.json")).unwrap();
|
||
let spec = Path::new(env!("CARGO_MANIFEST_DIR")).join("../neural-conformance/vectors/gradient/model");
|
||
let both = ModelRegistry::new().register(HYP, dir.join("g_s.whm")).register(CAP_NEURAL_INT_SYNTH, spec);
|
||
for fallback in [None, Some(CAP_NEURAL_INT_SYNTH)] {
|
||
match decode_envelope(&env("improve", None, fallback, json.clone()), &both) {
|
||
Err(DecodeError::InvalidPayload(why)) => assert!(why.contains("bad_magic") && why.contains(HYP), "{why}"),
|
||
other => panic!("a JSON form must be refused as bad_magic, got {}", refusal(other)),
|
||
}
|
||
}
|
||
// An unpinned set that lacks a member: the payload is checked first, so a JSON form
|
||
// is still bad_magic, and a WIH1 is set_incomplete, naming the member, so dispatch
|
||
// moves on as it does for any decoder.
|
||
let (lack, _) = int_hyper_set_dir("lack", &["entropy.json", "g_s.whm", "h_s.whm"]);
|
||
std::fs::remove_file(lack.join("h_s.whm")).unwrap();
|
||
let reg = ModelRegistry::new().register(HYP, lack.join("g_s.whm"));
|
||
match decode_envelope(&env("improve", None, None, json.clone()), ®) {
|
||
Err(DecodeError::InvalidPayload(why)) => assert!(why.contains("bad_magic"), "{why}"),
|
||
other => panic!("a JSON form must be refused as bad_magic, got {}", refusal(other)),
|
||
}
|
||
let r = decode_envelope(&env("improve", None, None, payload.clone()), ®);
|
||
assert!(matches!(r, Err(DecodeError::PriorMismatch(wai::container::PinRefusal::SetIncomplete { ref path })) if path == "h_s.whm"), "{}", refusal(r));
|
||
std::fs::remove_dir_all(&dir).ok();
|
||
std::fs::remove_dir_all(&part).ok();
|
||
std::fs::remove_dir_all(&lack).ok();
|
||
}
|
||
|
||
/// The integer video, audio and splat capabilities through the neural dispatch:
|
||
/// `wai.video.int_motion`, which reads no parameter set, advertised with
|
||
/// `ModelRegistry::builtin`; `wai.audio.int_codec` and `wai.splat.int_codec`, each
|
||
/// registered as its one file, unpinned and under a form-less pin over that file. Each
|
||
/// decodes to `Decoded::Int` in the integer form its digest is taken over, with the
|
||
/// committed SHA-256. `builtin` does not advertise a capability that reads a set.
|
||
#[cfg(all(feature = "video_int", feature = "audio_int", feature = "splat_int"))]
|
||
#[test]
|
||
fn integer_media_decode_through_the_neural_dispatch() {
|
||
use wai::codecs::{CAP_AUDIO_INT_CODEC as AUD, CAP_NEURAL_INT_HYPER, CAP_SPLAT_INT_CODEC as SPL, CAP_VIDEO_INT_MOTION as MOT};
|
||
use wai::int_payload::IntDecoded;
|
||
let v = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors");
|
||
let rd = |rel: &str| std::fs::read(v.join(rel)).unwrap();
|
||
let side = |rel: &str, key: &str, field: &str| -> String {
|
||
let j: serde_json::Value = serde_json::from_slice(&rd(rel)).unwrap();
|
||
j[key][field].as_str().unwrap().to_string()
|
||
};
|
||
let env = |cap: &str, prior: Option<Prior>, payload: Vec<u8>| {
|
||
let mut cond = Conditioning::new("bytes");
|
||
if let Some(p) = prior {
|
||
cond = cond.with_prior(p);
|
||
}
|
||
Wai::new(Manifest::new("binary", "improve", ModelRequirement::new(cap), cond), payload)
|
||
};
|
||
let reg = ModelRegistry::new()
|
||
.builtin(MOT)
|
||
.register(AUD, v.join("int_audio/audio_model.bin"))
|
||
.register(SPL, v.join("int_splat/splat_gs.bin"));
|
||
assert!(reg.advertises(MOT));
|
||
assert!(!ModelRegistry::new().builtin(CAP_NEURAL_INT_HYPER).advertises(CAP_NEURAL_INT_HYPER));
|
||
|
||
match decode_envelope(&env(MOT, None, rd("int_video/int_video_bitstream.wiv1")), ®) {
|
||
Ok(Decoded::Int(IntDecoded::Video { frames, width: 48, height: 48 })) => {
|
||
let per: Vec<u8> = frames.iter().flat_map(|f| wai::pin::sha256(f)).collect();
|
||
assert_eq!(wai::pin::sha256_hex(&per), side("int_video/sha256.json", "int_video", "clip_sha256"));
|
||
}
|
||
other => panic!("int_motion: {}", refusal(other)),
|
||
}
|
||
let model = rd("int_audio/audio_model.bin");
|
||
for prior in [None, Some(Prior::new("audio", wai::pin::sha256_hex(&model), "decode-equivalence"))] {
|
||
match decode_envelope(&env(AUD, prior, rd("int_audio/audio_bitstream.wia1")), ®) {
|
||
Ok(Decoded::Int(IntDecoded::Pcm { samples, sample_rate: 16_000, channels: 1 })) => {
|
||
let le: Vec<u8> = samples.iter().flat_map(|s| s.to_le_bytes()).collect();
|
||
assert_eq!(wai::pin::sha256_hex(&le), side("int_audio/sha256.json", "audio", "sha256"));
|
||
}
|
||
other => panic!("int_codec: {}", refusal(other)),
|
||
}
|
||
}
|
||
let gs = rd("int_splat/splat_gs.bin");
|
||
for prior in [None, Some(Prior::new("splat", wai::pin::sha256_hex(&gs), "decode-equivalence"))] {
|
||
match decode_envelope(&env(SPL, prior, rd("int_splat/splat.wis1")), ®) {
|
||
Ok(Decoded::Int(IntDecoded::Attr { bytes, n_attr: 14, s: 32, .. })) => {
|
||
assert_eq!(wai::pin::sha256_hex(&bytes), side("int_splat/sha256.json", "splat", "sha256"));
|
||
}
|
||
other => panic!("splat: {}", refusal(other)),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// An integer capability this build has no decoder for (here `wai.video.int_motion`,
|
||
/// without `video_int`) is refused with `unsupported_capability`, which is not final:
|
||
/// SPEC §4 does not take step 3 with a capability the sink cannot decode, so dispatch
|
||
/// moves on to the fallback, which decodes the payload. Without a fallback the
|
||
/// capability is reported as unknown.
|
||
#[cfg(not(feature = "video_int"))]
|
||
#[test]
|
||
fn an_integer_capability_without_a_decoder_moves_on_to_the_fallback() {
|
||
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("../neural-conformance/vectors/gradient");
|
||
let latent = std::fs::read(src.join("latent.bin")).unwrap();
|
||
let (mot, synth) = ("wai.video.int_motion", "wai.neural.int_synth");
|
||
let reg = ModelRegistry::new().builtin(mot).register(synth, src.join("model"));
|
||
let env = |fallback: Option<&str>| {
|
||
let mut req = ModelRequirement::new(mot);
|
||
if let Some(f) = fallback {
|
||
req = req.with_fallback(f);
|
||
}
|
||
Wai::new(Manifest::new("image", "improve", req, Conditioning::new("int_synth")), latent.clone())
|
||
};
|
||
match decode_envelope(&env(Some(synth)), ®) {
|
||
Ok(Decoded::Image(img)) => assert_eq!((img.width, img.height), (38, 38)),
|
||
other => panic!("the fallback must decode, got {}", refusal(other)),
|
||
}
|
||
assert!(matches!(decode_envelope(&env(None), ®), Err(DecodeError::UnknownCapability(ref c)) if c == mot));
|
||
}
|
||
|
||
/// A key log stand-in that holds the capability's sets to §7.9 and grants only the
|
||
/// digests it holds: an unpinned set is authorised only under one of them.
|
||
#[derive(Debug)]
|
||
struct Grants(Vec<String>);
|
||
|
||
impl wai::pin::PinAuthority for Grants {
|
||
fn check(&self, cap: &str, prior: &Prior) -> Result<(), wai::container::PinRefusal> {
|
||
self.check_unpinned(cap, std::slice::from_ref(&prior.sha256))
|
||
}
|
||
fn check_unpinned(&self, cap: &str, digests: &[String]) -> Result<(), wai::container::PinRefusal> {
|
||
if digests.iter().any(|d| self.0.contains(d)) {
|
||
return Ok(());
|
||
}
|
||
Err(wai::container::PinRefusal::NotAuthorised { capability: cap.into(), check: None, reason: None })
|
||
}
|
||
}
|
||
|
||
/// A listing beside the registered path identifies a set only when it matches the
|
||
/// files the decode reads. A key log that grants only the publisher's listing refuses
|
||
/// an unpinned decode of other files (`prior_not_authorised`), with a copy of that
|
||
/// listing beside them or not; the publisher's own files beside the same listing
|
||
/// decode. The same holds for a one-file capability registered as its file, and for a
|
||
/// capability outside the integer path (resolved, not decoded: no model is needed).
|
||
#[test]
|
||
fn a_listing_that_does_not_match_the_files_read_authorises_nothing() {
|
||
use wai::codecs::{CAP_AUDIO_INT_CODEC as AUD, CAP_NEURAL_INT_HYPER as HYP};
|
||
let code = |r: Result<Decoded, DecodeError>| match r {
|
||
Ok(_) => "decoded".to_string(),
|
||
Err(e) => refusal(Err(e)),
|
||
};
|
||
let v = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors");
|
||
let root = std::env::temp_dir().join(format!("wai-stale-listing-{}", std::process::id()));
|
||
let set: std::collections::BTreeMap<String, Vec<u8>> = [("entropy.json", "int_hyper/tiny_entropy.json"), ("g_s.whm", "int_hyper/tiny_g_s.bin"), ("h_s.whm", "int_hyper/tiny_h_s.bin")]
|
||
.into_iter().map(|(k, f)| (k.to_string(), std::fs::read(v.join(f)).unwrap())).collect();
|
||
let view: std::collections::BTreeMap<String, &[u8]> = set.iter().map(|(k, b)| (k.clone(), b.as_slice())).collect();
|
||
let granted = wai::pin::listing_from(&view).unwrap();
|
||
let write = |dir: &Path, files: &std::collections::BTreeMap<String, Vec<u8>>, listing: bool| {
|
||
std::fs::create_dir_all(dir).unwrap();
|
||
for (k, b) in files {
|
||
std::fs::write(dir.join(k), b).unwrap();
|
||
}
|
||
if listing {
|
||
std::fs::write(dir.join("publisher.sha256"), &granted).unwrap();
|
||
}
|
||
};
|
||
let mut other = set.clone();
|
||
other.get_mut("entropy.json").unwrap().push(b' ');
|
||
let env = Wai::new(Manifest::new("image", "improve", ModelRequirement::new(HYP), Conditioning::new("bytes")),
|
||
std::fs::read(v.join("int_hyper/tiny_bitstream.wih1")).unwrap());
|
||
let reg = |dir: &Path| ModelRegistry::new().register(HYP, dir.to_path_buf()).with_key_log(Grants(vec![wai::pin::sha256_hex(&granted)]));
|
||
for (name, files, listing, want) in [
|
||
("other files", &other, false, "prior_not_authorised"),
|
||
("other files beside the granted listing", &other, true, "prior_not_authorised"),
|
||
("the granted files beside their listing", &set, true, "decoded"),
|
||
] {
|
||
let dir = root.join(name.replace(' ', "-"));
|
||
write(&dir, files, listing);
|
||
assert_eq!(code(decode_envelope(&env, ®(&dir))), want, "{name}");
|
||
}
|
||
|
||
// One file: the publisher's model.wam is granted under its listing; the sink's file
|
||
// differs in its last byte.
|
||
let model = std::fs::read(v.join("int_audio/audio_model.bin")).unwrap();
|
||
let one: std::collections::BTreeMap<String, &[u8]> = [("model.wam".to_string(), model.as_slice())].into_iter().collect();
|
||
let pub_listing = wai::pin::listing_from(&one).unwrap();
|
||
let mut tampered = model.clone();
|
||
*tampered.last_mut().unwrap() ^= 1;
|
||
let dir = root.join("audio");
|
||
std::fs::create_dir_all(&dir).unwrap();
|
||
std::fs::write(dir.join("model.wam"), &tampered).unwrap();
|
||
std::fs::write(dir.join("publisher.sha256"), &pub_listing).unwrap();
|
||
let envw = Wai::new(Manifest::new("audio", "improve", ModelRequirement::new(AUD), Conditioning::new("bytes")),
|
||
std::fs::read(v.join("int_audio/audio_bitstream.wia1")).unwrap());
|
||
let reg = ModelRegistry::new().register(AUD, dir.join("model.wam")).with_key_log(Grants(vec![wai::pin::sha256_hex(&pub_listing)]));
|
||
assert_eq!(code(decode_envelope(&envw, ®)), "prior_not_authorised", "a one-file set beside a stale listing");
|
||
|
||
// A capability outside the integer path: the listing must match the files beside it
|
||
// and name the registered file.
|
||
let cap = "wai.neural.bmshj2018";
|
||
let dir = root.join("onnx");
|
||
std::fs::create_dir_all(&dir).unwrap();
|
||
std::fs::write(dir.join("decoder.onnx"), b"publisher").unwrap();
|
||
let onnx: std::collections::BTreeMap<String, &[u8]> = [("decoder.onnx".to_string(), &b"publisher"[..])].into_iter().collect();
|
||
let l = wai::pin::listing_from(&onnx).unwrap();
|
||
std::fs::write(dir.join("publisher.sha256"), &l).unwrap();
|
||
let reg = || ModelRegistry::new().register(cap, dir.join("decoder.onnx")).with_key_log(Grants(vec![wai::pin::sha256_hex(&l)]));
|
||
assert!(reg().resolve_verified(cap, None).is_ok(), "the granted file beside its listing resolves");
|
||
std::fs::write(dir.join("decoder.onnx"), b"other").unwrap();
|
||
assert!(matches!(reg().resolve_verified(cap, None), Err(DecodeError::PriorMismatch(ref r)) if r.code() == "prior_not_authorised"),
|
||
"another file beside the granted listing is not authorised");
|
||
std::fs::remove_dir_all(&root).ok();
|
||
}
|
||
|
||
/// Outside the integer path, a granted listing authorises the files it names and
|
||
/// nothing else: at a sink whose key log holds the capability's sets to §7.9, the
|
||
/// unpinned decode reads only the part §7.9 authorised of what it read once
|
||
/// (`ParamSource::Checked`), and a file the decode needs that the part does not name
|
||
/// is `set_incomplete`, never read from disk. A `wai.neural.bmshj2018` payload reads
|
||
/// its CDF tables (`cdfs_q3.json`) before it loads the decoder, so a decode that read
|
||
/// the unauthorised tables beside the decoder would fail parsing them instead.
|
||
///
|
||
/// - the decoder registered as its file, beside a granted listing that names only
|
||
/// `decoder.onnx` and an unauthorised `cdfs_q3.json`: `set_incomplete`;
|
||
/// - the directory registered, beside a granted listing that names only `notes.txt`:
|
||
/// `set_incomplete`;
|
||
/// - a granted listing of both files: the decode reads the tables from the set;
|
||
/// - a one-file capability granted under its file's digest: that file alone;
|
||
/// - a key log that only revokes: every file of the decoder's directory, read once.
|
||
#[test]
|
||
fn a_granted_listing_authorises_only_the_files_it_names() {
|
||
use wai::neural::ParamSource;
|
||
use wai::pin::Verified;
|
||
let bm = "wai.neural.bmshj2018";
|
||
let dir = std::env::temp_dir().join(format!("wai-granted-listing-{}", std::process::id()));
|
||
std::fs::create_dir_all(&dir).unwrap();
|
||
let files: [(&str, &[u8]); 3] = [("decoder.onnx", b"the publisher's decoder"), ("cdfs_q3.json", b"{\"unauthorised\": true}"), ("notes.txt", b"an unrelated file")];
|
||
for (n, b) in files {
|
||
std::fs::write(dir.join(n), b).unwrap();
|
||
}
|
||
let listing = |names: &[&str]| {
|
||
let set: std::collections::BTreeMap<String, &[u8]> =
|
||
files.iter().filter(|(n, _)| names.contains(n)).map(|(n, b)| (n.to_string(), *b)).collect();
|
||
let l = wai::pin::listing_from(&set).unwrap();
|
||
std::fs::write(dir.join("granted.sha256"), &l).unwrap();
|
||
wai::pin::sha256_hex(&l)
|
||
};
|
||
// h 1, w 1, an empty stream, quality 3, one channel, a 1 × 1 latent.
|
||
let mut payload = Vec::new();
|
||
for v in [1u32, 1, 0, 3] {
|
||
payload.extend_from_slice(&v.to_le_bytes());
|
||
}
|
||
payload.extend_from_slice(&[1, 0]);
|
||
let env = Wai::new(Manifest::new("image", "improve", ModelRequirement::new(bm), Conditioning::new("bytes")), payload);
|
||
let names = |p: &ParamSource| match p {
|
||
ParamSource::Checked { set: Verified::Set { files, .. }, .. } => files.keys().cloned().collect::<Vec<_>>().join(","),
|
||
ParamSource::Checked { set: Verified::File(_), .. } => "one file".into(),
|
||
ParamSource::Unpinned(_) => "the path".into(),
|
||
other => format!("{other:?}"),
|
||
};
|
||
let case = |path: PathBuf, granted: &[&str]| {
|
||
let reg = ModelRegistry::new().register(bm, path).with_key_log(Grants(vec![listing(granted)]));
|
||
(names(®.resolve_verified(bm, None).unwrap()), refusal(decode_envelope(&env, ®)))
|
||
};
|
||
let incomplete = |r: &str| r == "set_incomplete";
|
||
|
||
let (set, r) = case(dir.join("decoder.onnx"), &["decoder.onnx"]);
|
||
assert_eq!(set, "decoder.onnx");
|
||
assert!(incomplete(&r), "a listing of the decoder alone does not authorise the tables beside it: {r}");
|
||
let (set, r) = case(dir.clone(), &["notes.txt"]);
|
||
assert_eq!(set, "notes.txt");
|
||
assert!(incomplete(&r), "a listing of another file does not authorise the directory: {r}");
|
||
let (set, r) = case(dir.join("decoder.onnx"), &["cdfs_q3.json", "decoder.onnx"]);
|
||
assert_eq!(set, "cdfs_q3.json,decoder.onnx");
|
||
assert!(r.contains("cdf parse cdfs_q3.json"), "the listed tables are read from the set: {r}");
|
||
|
||
// A one-file capability granted under its file's digest decodes from that file alone.
|
||
std::fs::remove_file(dir.join("granted.sha256")).unwrap();
|
||
let enc = "wai.neural.encodec32";
|
||
let digest = wai::pin::sha256_hex(files[0].1);
|
||
let reg = ModelRegistry::new().register(enc, dir.join("decoder.onnx")).with_key_log(Grants(vec![digest.clone()]));
|
||
match reg.resolve_verified(enc, None).unwrap() {
|
||
ParamSource::Checked { set: Verified::File(b), .. } => assert_eq!(b, files[0].1),
|
||
other => panic!("expected the authorised file, got {other:?}"),
|
||
}
|
||
// A key log that only revokes holds nothing to §7.9: the decode reads every file of
|
||
// the decoder's directory, read once.
|
||
let reg = ModelRegistry::new().register(bm, dir.join("decoder.onnx")).with_key_log(Revokes(vec!["00".repeat(32)]));
|
||
assert_eq!(names(®.resolve_verified(bm, None).unwrap()), "cdfs_q3.json,decoder.onnx,notes.txt");
|
||
std::fs::remove_dir_all(&dir).ok();
|
||
}
|
||
|
||
/// At a sink whose key log only revokes, an unpinned decode outside the integer path
|
||
/// is checked over every digest a pin over what it reads could name, and reads those
|
||
/// bytes alone (SPEC §4, *Module authorisation*). With no listing beside the files, a
|
||
/// key log that revokes the listing of `{decoder.onnx, cdfs_q3.json}` still refuses
|
||
/// them (`prior_revoked`) through the listing derived from the directory, whether the
|
||
/// decoder or the directory is registered; one that revokes a member's own digest
|
||
/// refuses them too. The bytes were read once: tables swapped on disk after the check
|
||
/// are not the tables the decode reads. With no key log, the decode reads the path.
|
||
#[test]
|
||
fn a_revoke_only_key_log_checks_and_reads_the_set_once() {
|
||
use wai::neural::{decode_payload, ParamSource};
|
||
use wai::pin::Verified;
|
||
let bm = "wai.neural.bmshj2018";
|
||
let root = std::env::temp_dir().join(format!("wai-revoke-only-{}", std::process::id()));
|
||
let (decoder, cdfs_a, cdfs_b): (&[u8], &[u8], &[u8]) = (b"the publisher's decoder bytes", b"cdfs A: not JSON", b"{\"channels\": 1}");
|
||
let mk = |name: &str| {
|
||
let d = root.join(name);
|
||
std::fs::create_dir_all(&d).unwrap();
|
||
std::fs::write(d.join("decoder.onnx"), decoder).unwrap();
|
||
std::fs::write(d.join("cdfs_q3.json"), cdfs_a).unwrap();
|
||
d
|
||
};
|
||
let set: std::collections::BTreeMap<String, &[u8]> =
|
||
[("decoder.onnx".to_string(), decoder), ("cdfs_q3.json".to_string(), cdfs_a)].into_iter().collect();
|
||
let listing = wai::pin::listing_from(&set).unwrap();
|
||
let revoked = wai::pin::sha256_hex(&listing);
|
||
let mut payload = Vec::new();
|
||
for v in [1u32, 1, 0, 3] {
|
||
payload.extend_from_slice(&v.to_le_bytes());
|
||
}
|
||
payload.extend_from_slice(&[1, 0]);
|
||
let env = Wai::new(Manifest::new("image", "improve", ModelRequirement::new(bm), Conditioning::new("bytes")), payload.clone());
|
||
|
||
let r = mk("revoked");
|
||
std::fs::write(r.join("set.sha256"), &listing).unwrap();
|
||
let file_reg = || ModelRegistry::new().register(bm, r.join("decoder.onnx"));
|
||
assert_eq!(refusal(decode_envelope(&env, &file_reg().with_key_log(Revokes(vec![revoked.clone()])))), "prior_revoked",
|
||
"the revoked set beside its listing");
|
||
std::fs::remove_file(r.join("set.sha256")).unwrap();
|
||
assert_eq!(refusal(decode_envelope(&env, &file_reg().with_key_log(Revokes(vec![revoked.clone()])))), "prior_revoked",
|
||
"the decoder registered, no listing beside it");
|
||
let dir_reg = ModelRegistry::new().register(bm, r.clone()).with_key_log(Revokes(vec![revoked.clone()]));
|
||
assert_eq!(refusal(decode_envelope(&env, &dir_reg)), "prior_revoked", "the directory registered, no listing beside it");
|
||
let member = Revokes(vec![wai::pin::sha256_hex(cdfs_a)]);
|
||
assert_eq!(refusal(decode_envelope(&env, &file_reg().with_key_log(member))), "prior_revoked", "a member's own digest revoked");
|
||
|
||
// Read once: the tables swapped after the check are not read. The decode reads A
|
||
// (not JSON: "expected value"); B would parse and lack a field.
|
||
let t = mk("toctou");
|
||
let reg = ModelRegistry::new().register(bm, t.join("decoder.onnx")).with_key_log(Revokes(vec![]));
|
||
let params = reg.resolve_verified(bm, None).unwrap();
|
||
std::fs::write(t.join("cdfs_q3.json"), cdfs_b).unwrap();
|
||
std::fs::write(t.join("cdfs_q4.json"), cdfs_b).unwrap();
|
||
let got = refusal(decode_payload(bm, &payload, ¶ms));
|
||
assert!(got.contains("cdf parse cdfs_q3.json: expected value"), "the decode read the tables checked: {got}");
|
||
match ¶ms {
|
||
ParamSource::Checked { set: Verified::Set { files, .. }, model } => {
|
||
assert_eq!(files.keys().collect::<Vec<_>>(), ["cdfs_q3.json", "decoder.onnx"]);
|
||
assert_eq!(model.as_deref(), Some("decoder.onnx"));
|
||
}
|
||
other => panic!("expected the files read once, got {other:?}"),
|
||
}
|
||
|
||
// No key log: no check 5, and the decode reads the path, as it always has.
|
||
assert!(matches!(ModelRegistry::new().register(bm, t.join("decoder.onnx")).resolve_verified(bm, None), Ok(ParamSource::Unpinned(_))));
|
||
std::fs::remove_dir_all(&root).ok();
|
||
}
|
||
|
||
/// The reference sink opens no encryption scheme, so an envelope whose manifest
|
||
/// declares one is inert at it (SPEC §6.4 step 2, `unsupported_encryption`): a
|
||
/// payload sent in the clear under a claim of encryption is not decoded as plaintext,
|
||
/// and a tool's JSON form under that claim is not read as a payload either. The same
|
||
/// payload without the claim decodes; a `WAI2` gives every entry the reason, also when
|
||
/// its `security` member is not a security object.
|
||
#[test]
|
||
fn an_encrypted_envelope_is_inert_at_the_reference_sink() {
|
||
use wai::codecs::CAP_NEURAL_INT_HYPER as HYP;
|
||
use wai::container::{Rendition, Security, WaiMulti};
|
||
use wai::neural::decode_multi;
|
||
let v = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors");
|
||
let dir = std::env::temp_dir().join(format!("wai-encrypted-{}", std::process::id()));
|
||
std::fs::create_dir_all(&dir).unwrap();
|
||
for (k, f) in [("entropy.json", "int_hyper/tiny_entropy.json"), ("g_s.whm", "int_hyper/tiny_g_s.bin"), ("h_s.whm", "int_hyper/tiny_h_s.bin")] {
|
||
std::fs::copy(v.join(f), dir.join(k)).unwrap();
|
||
}
|
||
let reg = ModelRegistry::new().register(HYP, dir.clone());
|
||
let (wih1, json) = (std::fs::read(v.join("int_hyper/tiny_bitstream.wih1")).unwrap(), std::fs::read(v.join("int_hyper/tiny_bitstream.json")).unwrap());
|
||
let env = |payload: &[u8], security: Option<Security>| {
|
||
let mut m = Manifest::new("image", "improve", ModelRequirement::new(HYP), Conditioning::new("bytes"));
|
||
m.security = security;
|
||
Wai::from_bytes(&Wai::new(m, payload.to_vec()).to_emit_bytes().unwrap()).unwrap()
|
||
};
|
||
assert!(matches!(decode_envelope(&env(&wih1, None), ®), Ok(Decoded::Image(_))), "in the clear, it decodes");
|
||
for payload in [&wih1, &json] {
|
||
match decode_envelope(&env(payload, Some(Security::new("aes-256-gcm", 7))), ®) {
|
||
Err(DecodeError::Encrypted(why)) => assert!(why.starts_with("unsupported_encryption: "), "{why}"),
|
||
other => panic!("an encrypted envelope must be inert, got {}", refusal(other)),
|
||
}
|
||
}
|
||
|
||
let multi = |security: Option<serde_json::Value>| {
|
||
let mut m = WaiMulti::new("image", "improve", serde_json::Value::Null, vec![Rendition::media(HYP, "bytes", wih1.clone())]);
|
||
if let Some(s) = security {
|
||
m.manifest.extra.insert("security".into(), s);
|
||
}
|
||
WaiMulti::from_bytes(&m.to_bytes().unwrap()).unwrap()
|
||
};
|
||
assert!(matches!(decode_multi(&multi(None), ®), Ok((0, Decoded::Image(_)))));
|
||
for s in [serde_json::json!({"encryption": "mls-sframe", "kid": 0}), serde_json::json!(true)] {
|
||
match decode_multi(&multi(Some(s.clone())), ®) {
|
||
Err(DecodeError::NoRendition(skips)) => assert_eq!(skips, vec![(0, "unsupported_encryption".to_string())], "{s}"),
|
||
other => panic!("{s}: expected every entry inert, got {:?}", other.map(|(i, _)| i)),
|
||
}
|
||
}
|
||
std::fs::remove_dir_all(&dir).ok();
|
||
}
|
||
|
||
/// The neural registry's pinned path runs check 5 over every identity of the bytes it
|
||
/// verified: a one-file set whose file digest a key log revokes is refused through a
|
||
/// set-form pin over a listing of that file.
|
||
#[test]
|
||
fn a_pinned_set_revoked_under_its_file_digest_is_refused_through_a_listing_pin() {
|
||
use wai::codecs::{CAP_AUDIO_INT_CODEC as AUD, CAP_PRIOR_SET};
|
||
let v = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors");
|
||
let model = std::fs::read(v.join("int_audio/audio_model.bin")).unwrap();
|
||
let dir = std::env::temp_dir().join(format!("wai-listing-pin-{}", std::process::id()));
|
||
std::fs::create_dir_all(&dir).unwrap();
|
||
std::fs::write(dir.join("model.wam"), &model).unwrap();
|
||
let one: std::collections::BTreeMap<String, &[u8]> = [("model.wam".to_string(), model.as_slice())].into_iter().collect();
|
||
let listing = wai::pin::listing_from(&one).unwrap();
|
||
std::fs::write(dir.join("model.sha256"), &listing).unwrap();
|
||
let pin = Prior::new("m", wai::pin::sha256_hex(&listing), "decode-equivalence").with_form(CAP_PRIOR_SET);
|
||
let plain = ModelRegistry::new().register(AUD, dir.join("model.wam"));
|
||
assert!(plain.verify(AUD, &pin).is_ok(), "the pin verifies");
|
||
let revoking = ModelRegistry::new().register(AUD, dir.join("model.wam")).with_key_log(Revokes(vec![wai::pin::sha256_hex(&model)]));
|
||
assert_eq!(revoking.verify(AUD, &pin).map(|_| ()).map_err(|r| r.code()), Err("prior_revoked"));
|
||
std::fs::remove_dir_all(&dir).ok();
|
||
}
|