All checks were successful
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Successful in 6s
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 7s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
wai-app and Create / wai-app · does this change touch it (pull_request) Successful in 6s
wai-audit collector and player integration / does this change touch it (pull_request) Successful in 6s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 6s
WAI operation catalogue / does this change touch it (pull_request) Successful in 6s
WAI conformance / does this change touch it (pull_request) Successful in 6s
WAI energy receipts / does this change touch it (pull_request) Successful in 5s
WAI examples / WAI examples · does this change touch them (pull_request) Successful in 6s
wai-fleet service / does this change touch it (pull_request) Successful in 6s
WAI JPEG-AI whole decode (pinned inputs, required) / does this change touch it (pull_request) Successful in 7s
wai-ledger service / does this change touch it (pull_request) Successful in 6s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Successful in 1m25s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 6s
wai-quantum for JavaScript / does this change touch it (pull_request) Successful in 6s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 6s
wai-sink-android native sink library / does this change touch it (pull_request) Successful in 6s
wai-sink-swift native sink package / does this change touch it (pull_request) Successful in 6s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Successful in 1m57s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m7s
WAI Studio / does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 6s
wai-web wasm32 / does this change touch it (pull_request) Successful in 6s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (pull_request) Successful in 29s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (pull_request) Successful in 2m1s
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (pull_request) Successful in 2m59s
wai-audit collector and player integration / collector · linux (pull_request) Successful in 1m1s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 26s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 4m19s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 16s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m4s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 1m15s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (pull_request) Successful in 5m15s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 6m19s
wai-fleet service / service · macos (pull_request) Successful in 1m2s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (pull_request) Successful in 2m46s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 1m43s
wai-ledger service / service · macos (pull_request) Successful in 1m24s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 1m2s
WAI operation catalogue / the catalogue and every door · linux (pull_request) Successful in 4m18s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 9m19s
wai-pack / wai-pack · macos (pull_request) Successful in 3m8s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 30s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 7m49s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 3m23s
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m39s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 3m9s
wai-fleet service / service · linux (pull_request) Successful in 2m5s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 3m28s
wai-ledger service / service · linux (pull_request) Successful in 1m43s
wai-pack / wai-pack · linux (pull_request) Successful in 1m55s
wai-quantum for JavaScript / javascript · linux (pull_request) Successful in 3m0s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 1m10s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 16m12s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 11m32s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 4m51s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 13m12s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 15m2s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 4m3s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 16m34s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m40s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 17m22s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m44s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 5m46s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 34s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 17s
WAI examples / WAI examples · portable · linux (pull_request) Successful in 9m28s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 5m7s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 14m49s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m34s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m16s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 11m4s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 13m6s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 5m58s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 10m55s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 9m51s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 10m15s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 9m21s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 30m14s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h4m7s
Every change to WAI added an entry at the head of wai/CHANGELOG.md, and every new module appended to the hand-kept lists of .github/workflows/wai-conformance.yml. So any two branches conflicted on those lines and had to merge one after another. This moves both into files a change adds. - Changelog entries: a change adds wai/changes/<yyyy-mm-dd>-<slug>.md holding the entry text. scripts/changelog-assemble.py renders the entries into CHANGELOG.md's Unreleased section for a release, in a stable order. scripts/check-changelog-fragment.py, run by repo-checks on every pull request, fails a pull request that changes wai/ and adds or edits no entry file directly in wai/changes/ (a release passes, and so does a commit with the trailer line "Changelog: none"). A pull request opened before the check reached main passes as it is; one opened after it that adds its entry to the head of Unreleased fails and is told where the entry goes. wai breaking reads breaking changes from entry files too. - The conformance workflow: each per-module verifier step is a file in .github/wai-conformance/verifiers/, each corpus a file in corpora/, and the integer-kernel tests carry a tag line. scripts/wai-conformance-lists.py derives every list the workflow kept by hand (WAI_FEATURES, the union --bin and --test lists, the features-alone lists, the kernel and corpus steps) from those files when the workflow runs. Its `selftest --exact` holds the derived lists to the hand lists of the commit before this one, and its dry-run prints every command each job runs: the dry-runs before and after this change differ only in the gate's path list, which gains the two paths this change adds. - What stays shared, and how to insert there, is written in wai/CONTRIBUTING.md and wai/changes/README.md: wai-rs Cargo.toml features and [[test]] entries, lib.rs, registry rows, wai/.gitattributes and the live corpus table take an insert beside the related entry, never at the end. Co-Authored-By: Claude Opus 5.5 <[email protected]>
336 lines
17 KiB
Rust
336 lines
17 KiB
Rust
// wai-conformance: int-kernels
|
|
//! `wai.neural.int_mlicpp` on its committed conformance vector
|
|
//! (`tests/vectors/int_mlicpp/`, written by `tools/wai_int_mlicpp_export.py`). No
|
|
//! test here skips: the vector is in the repository.
|
|
//!
|
|
//! The vector's weights are synthetic (a fixed seed, quantised the way a trained
|
|
//! model's would be). These tests establish decode-equivalence — that this decoder
|
|
//! reproduces the export tool's independent integer reference byte for byte, derives
|
|
//! every scale bucket the encoder used, and does so under every kernel policy and
|
|
//! thread count — and nothing about compression quality.
|
|
//!
|
|
//! What they pin, in `expected.json` and again as literals below so that a changed
|
|
//! vector cannot change them unnoticed: FNV-1a of the decoded RGB, and FNV-1a over the
|
|
//! y stream's bucket indices (u16 LE) and residual symbols (i32 LE) in coding order.
|
|
//! The SHA-256 of the pixels, of the model bundle and of the payload are checked by
|
|
//! the `byte-exact-conformance` golden, whose platform matrix runs the same decode.
|
|
#![cfg(feature = "neural_int")]
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use wai::container::Wai;
|
|
use wai::int_hyper_synth::{fnv1a, IntLayer};
|
|
use wai::int_kernels::{with_policy, Policy};
|
|
use wai::int_mlicpp::{decode, decode_traced, parse_header, Header, IntMlicImage, IntMlicModel, Trace};
|
|
|
|
const RGB_FNV1A: &str = "7c0b9a4b3423d775";
|
|
const BUCKET_FNV1A: &str = "2d30432f4fb206a0";
|
|
const SYMBOL_FNV1A: &str = "14ace1d4ad3a2446";
|
|
const MODEL_SHA256: &str = "7ef44f3b1fc4a2df6c3e3b68460f595cae3d31cb7f9018699e29e738340272a4";
|
|
|
|
fn dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_mlicpp")
|
|
}
|
|
fn read(name: &str) -> Vec<u8> {
|
|
let p = dir().join(name);
|
|
std::fs::read(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()))
|
|
}
|
|
fn model() -> IntMlicModel {
|
|
IntMlicModel::from_bundle(&read("model.wmb")).expect("the committed model bundle")
|
|
}
|
|
fn envelope() -> Wai {
|
|
Wai::from_bytes(&read("stream.wai")).expect("the committed envelope")
|
|
}
|
|
fn expected() -> serde_json::Value {
|
|
serde_json::from_slice(&read("expected.json")).expect("expected.json")
|
|
}
|
|
fn hex(h: u64) -> String {
|
|
format!("{h:016x}")
|
|
}
|
|
fn bucket_hash(t: &Trace) -> String {
|
|
let b: Vec<u8> = t.buckets.iter().flat_map(|&b| u16::try_from(b).expect("bucket fits u16").to_le_bytes()).collect();
|
|
hex(fnv1a(&b))
|
|
}
|
|
fn symbol_hash(t: &Trace) -> String {
|
|
let b: Vec<u8> = t.symbols.iter().flat_map(|s| s.to_le_bytes()).collect();
|
|
hex(fnv1a(&b))
|
|
}
|
|
|
|
#[test]
|
|
fn the_committed_vector_decodes_to_its_pinned_image() {
|
|
let (env, want) = (envelope(), expected());
|
|
let img = decode(&model(), &env.payload).expect("decode");
|
|
assert_eq!((img.width, img.height), (64, 64));
|
|
assert_eq!(img.rgb.len(), 64 * 64 * 3);
|
|
let got = hex(fnv1a(&img.rgb));
|
|
assert_eq!(got, want["rgb_fnv1a"].as_str().unwrap(), "the export tool's integer reference");
|
|
assert_eq!(got, RGB_FNV1A, "the pinned value");
|
|
}
|
|
|
|
/// The property the float capability lacks: the decoder derives, from its own
|
|
/// integer networks, exactly the bucket the encoder used for every element — and
|
|
/// reads exactly the encoder's symbol with it.
|
|
#[test]
|
|
fn the_decoder_derives_every_bucket_the_encoder_used() {
|
|
let (env, want, m) = (envelope(), expected(), model());
|
|
let (trace, img) = decode_traced(&m, &env.payload);
|
|
img.expect("decode");
|
|
assert_eq!(trace.buckets.len() as u64, want["y_symbols"].as_u64().unwrap());
|
|
assert_eq!(bucket_hash(&trace), want["bucket_fnv1a"].as_str().unwrap());
|
|
assert_eq!(bucket_hash(&trace), BUCKET_FNV1A);
|
|
assert_eq!(symbol_hash(&trace), want["symbol_fnv1a"].as_str().unwrap());
|
|
assert_eq!(symbol_hash(&trace), SYMBOL_FNV1A);
|
|
// What the vector exercises: every scale bucket, and the rANS escape (a residual
|
|
// outside its table, coded in bypass).
|
|
let used: std::collections::BTreeSet<_> = trace.buckets.iter().copied().collect();
|
|
assert_eq!(used.len() as u64, want["buckets_used"].as_u64().unwrap());
|
|
assert_eq!(used.len(), m.tables.edges.len(), "every bucket of the scale table is used");
|
|
let escapes = trace.buckets.iter().zip(&trace.symbols)
|
|
.filter(|&(&b, &s)| { let v = s - m.tables.offsets[b]; v < 0 || v >= m.tables.cdf_lengths[b] as i32 - 2 })
|
|
.count();
|
|
assert_eq!(escapes as u64, want["y_escapes"].as_u64().unwrap());
|
|
assert!(escapes > 0, "the vector exercises the escape path");
|
|
}
|
|
|
|
/// The float MLIC++ decode desynchronised when its encoder evaluated the entropy
|
|
/// network with a different thread count from the decoder's. Here the fast kernels
|
|
/// split the work across 1, 2, 3, 5 or all available threads, or the scalar reference
|
|
/// runs: every bucket, every symbol and every pixel is the same.
|
|
#[test]
|
|
fn thread_count_cannot_move_a_bucket() {
|
|
let (env, m) = (envelope(), model());
|
|
let run = |p: Policy| -> (Trace, IntMlicImage) {
|
|
let (t, img) = with_policy(p, || decode_traced(&m, &env.payload));
|
|
(t, img.expect("decode"))
|
|
};
|
|
let want = run(Policy::reference());
|
|
assert_eq!(hex(fnv1a(&want.1.rgb)), RGB_FNV1A);
|
|
for p in [Policy::fast(1), Policy::fast(2), Policy::fast(3), Policy::fast(5), Policy::fast(0)] {
|
|
assert!(run(p) == want, "{p:?} differs from the reference kernels");
|
|
}
|
|
}
|
|
|
|
fn bump(l: &mut IntLayer, i: usize, by: i64, bias: bool) {
|
|
match l {
|
|
IntLayer::Conv { weight, bias: b, .. } | IntLayer::ConvT { weight, bias: b, .. } => {
|
|
if bias { b[i] += by } else { weight[i] += by }
|
|
}
|
|
_ => panic!("not a convolution"),
|
|
}
|
|
}
|
|
|
|
/// The pinned image is not vacuous: each of four model parameters, one on each path
|
|
/// that feeds a latent value or a pixel — a weight of the hyper-decoder's first layer,
|
|
/// of the entropy network's shared layer, a bias of its mean head, and a weight of the
|
|
/// synthesis's first layer — changed by one integer unit, and the decode no longer
|
|
/// reproduces it (it decodes a different image, or refuses the stream once its symbols
|
|
/// stop making sense). Four chosen parameters, not every one: a weight that multiplies
|
|
/// only zeros, or a unit a later rounding absorbs, can leave the image as it was. The
|
|
/// scale head is different by design; see `a_scale_matters_only_through_its_bucket`.
|
|
#[test]
|
|
fn one_unit_on_each_value_path_changes_the_image() {
|
|
let env = envelope();
|
|
type Change = fn(&mut IntMlicModel);
|
|
let cases: [(&str, Change); 4] = [
|
|
("h_s weight", |m| bump(&mut m.h_s.layers[0], 7, 1, false)),
|
|
("entropy weight", |m| bump(&mut m.entropy.layers[0], 1234, 1, false)),
|
|
("entropy mean bias", |m| bump(&mut m.entropy.layers[2], 8, 1, true)),
|
|
("g_s weight", |m| bump(&mut m.g_s.layers[0], 99, 1, false)),
|
|
];
|
|
for (what, change) in cases {
|
|
let mut m = model();
|
|
change(&mut m);
|
|
let same = decode(&m, &env.payload).is_ok_and(|img| hex(fnv1a(&img.rgb)) == RGB_FNV1A);
|
|
assert!(!same, "{what}: a one-unit change left the pinned image unchanged");
|
|
}
|
|
}
|
|
|
|
/// How the float decode failed, and why the integer one cannot: a scale selects a
|
|
/// distribution only through its bucket. Moved by one unit without crossing a table
|
|
/// edge, channel 0's scales change nothing — same buckets, same symbols, same image.
|
|
/// Moved across edges (by one whole unit of scale, `2^afrac`), the first element
|
|
/// whose bucket moves is where the decode leaves the encoder: its symbol is read
|
|
/// against another distribution, and so is everything after it. Two float decoders
|
|
/// that compute a scale on either side of an edge do exactly this (the float MLIC++
|
|
/// validation run diverged at symbol 416 of 12,288); two integer decoders compute
|
|
/// the same scale, so they cannot.
|
|
#[test]
|
|
fn a_scale_matters_only_through_its_bucket() {
|
|
let env = envelope();
|
|
let (truth, img) = decode_traced(&model(), &env.payload);
|
|
img.expect("decode");
|
|
let sigma_bias_ch0 = |by: i64| {
|
|
let mut m = model();
|
|
bump(&mut m.entropy.layers[2], 0, by, true);
|
|
decode_traced(&m, &env.payload)
|
|
};
|
|
|
|
let (t, img) = sigma_bias_ch0(1);
|
|
assert_eq!(t, truth, "one unit within the buckets: the same buckets and symbols");
|
|
assert_eq!(hex(fnv1a(&img.expect("decode").rgb)), RGB_FNV1A);
|
|
|
|
let (t, img) = sigma_bias_ch0(1 << model().afrac());
|
|
let first = (0..truth.buckets.len())
|
|
.find(|&i| t.buckets.get(i) != Some(&truth.buckets[i]) || t.symbols.get(i) != Some(&truth.symbols[i]))
|
|
.expect("the stream leaves the encoder somewhere");
|
|
assert!(first < t.buckets.len(), "the divergence is within what was decoded");
|
|
assert_ne!(t.buckets[first], truth.buckets[first], "the first difference is a bucket, not a symbol");
|
|
// Channel 0's anchors are the first 32 elements coded (slice 0, anchor pass).
|
|
assert!(first < 32, "the first moved bucket is one of channel 0's, at element {first}");
|
|
let outcome = match &img {
|
|
Ok(img) => format!("decodes to {}", hex(fnv1a(&img.rgb))),
|
|
Err(e) => format!("is refused ({e})"),
|
|
};
|
|
println!("scale +1.0 on channel 0: first moved bucket at element {first} ({} -> {}); the stream then {outcome}",
|
|
truth.buckets[first], t.buckets[first]);
|
|
assert!(img.map_or(true, |img| hex(fnv1a(&img.rgb)) != RGB_FNV1A), "the image is not the pinned one");
|
|
}
|
|
|
|
#[test]
|
|
fn the_envelope_pins_the_model_as_an_integer_exact_prior() {
|
|
let (env, want) = (envelope(), expected());
|
|
assert_eq!(env.capability(), "wai.neural.int_mlicpp");
|
|
assert_eq!(env.manifest.intent, "replicate");
|
|
let prior = env.prior().expect("a pinned prior");
|
|
assert_eq!(prior.sha256, MODEL_SHA256);
|
|
assert_eq!(prior.sha256, want["model_sha256"].as_str().unwrap());
|
|
assert!(prior.is_cross_hardware_exact(), "an integer-exact contract, in no declared form");
|
|
// SPEC §7: the registry class (NeuralIntegerExact) and the prior both back replicate.
|
|
assert_eq!(env.replicate_refusal(), None);
|
|
let h = parse_header(&env.payload).expect("header");
|
|
assert_eq!((h.cz, h.zh, h.zw, h.cy, h.yh, h.yw, h.slices), (4, 2, 2, 8, 8, 8, 4));
|
|
assert_eq!((h.width, h.height), (64, 64));
|
|
}
|
|
|
|
/// Damage to the committed payload — each header byte three ways, and random changes
|
|
/// in both streams — returns an image or a refusal, never a panic, in this
|
|
/// overflow-checked test build and in release alike.
|
|
#[test]
|
|
fn damaging_the_committed_payload_never_panics() {
|
|
let (pl, m) = (envelope().payload, model());
|
|
let mut state = 0x1A7E_4C10u64;
|
|
let mut next = move || {
|
|
state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
|
|
let mut z = state;
|
|
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
|
|
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
|
|
z ^ (z >> 31)
|
|
};
|
|
let (mut decoded, mut refused) = (0, 0);
|
|
let mut tally = |r: Result<IntMlicImage, _>| if r.is_ok() { decoded += 1 } else { refused += 1 };
|
|
for i in 0..Header::LEN {
|
|
for x in [0x01u8, 0x80, 0xFF] {
|
|
let mut p = pl.clone();
|
|
p[i] ^= x;
|
|
tally(decode(&m, &p));
|
|
}
|
|
}
|
|
for _ in 0..200 {
|
|
let mut p = pl.clone();
|
|
for _ in 0..1 + next() % 4 {
|
|
let i = Header::LEN + (next() as usize) % (p.len() - Header::LEN);
|
|
p[i] = next() as u8;
|
|
}
|
|
tally(decode(&m, &p));
|
|
}
|
|
assert!(decoded > 0 && refused > 0, "{decoded} decoded, {refused} refused");
|
|
}
|
|
|
|
// ---- the parameter-set pin over the committed vector (SPEC §3.1, §7.1) ----
|
|
|
|
const LISTING_SHA256: &str = "85a05b0dac15566c5a657ddeee253eaa9f54ce852ae04924f5b04151901d9d1b";
|
|
const STREAM_SET_SHA256: &str = "8f23e85cd3d2690817c8935c2473dc2ba3af3cb302e3dca3cf3feb60f1c263d0";
|
|
const RGB_SHA256: &str = "45040d17b4178b91a0547b435e57a50fa1021101e290a94d1c57e6fbb4583a10";
|
|
|
|
fn scratch(tag: &str) -> PathBuf {
|
|
let d = std::env::temp_dir().join(format!("wai-int-mlicpp-{tag}-{}", std::process::id()));
|
|
std::fs::create_dir_all(&d).unwrap();
|
|
d
|
|
}
|
|
|
|
/// The set-form twin of `stream.wai` (`tools/wai_int_mlicpp_set_form.py`): the same
|
|
/// payload, pinned through the listing of the one-file set. The pin verifies against
|
|
/// the committed listing and bundle, and the decode from the very bytes it verified
|
|
/// is the pinned image. A flipped listing is not held; a flipped bundle under an
|
|
/// intact listing is a part mismatch; a listing that names another file verifies, and
|
|
/// the decode then needs a file the set does not contain.
|
|
#[test]
|
|
fn set_form_pin_resolves_and_decodes_the_same_image() {
|
|
use wai::pin::{sha256_hex, verify, Held, READABLE_FORMS};
|
|
let set_bytes = read("stream_set.wai");
|
|
assert_eq!(sha256_hex(&set_bytes), STREAM_SET_SHA256);
|
|
assert_eq!(sha256_hex(&read("model.sha256")), LISTING_SHA256);
|
|
let set = Wai::from_bytes(&set_bytes).unwrap();
|
|
let legacy = envelope();
|
|
assert_eq!(set.payload, legacy.payload, "only the pin differs");
|
|
let pin = set.prior().expect("a pin");
|
|
assert_eq!((pin.sha256.as_str(), pin.form()), (LISTING_SHA256, Some("wai.prior.set")));
|
|
assert_eq!(set.replicate_refusal(), None, "a set-form pin declaring decode-equivalence backs replicate");
|
|
|
|
let v = verify("wai.neural.int_mlicpp", pin, &[Held::Listing(dir().join("model.sha256"))], READABLE_FORMS)
|
|
.expect("the committed listing and bundle verify");
|
|
let m = IntMlicModel::from_bundle(v.file("model.wmb").unwrap()).unwrap();
|
|
let img = decode(&m, &set.payload).unwrap();
|
|
assert_eq!(hex(fnv1a(&img.rgb)), RGB_FNV1A);
|
|
assert_eq!(sha256_hex(&img.rgb), RGB_SHA256);
|
|
|
|
// Twins on temporary copies.
|
|
let code = |d: &PathBuf| verify("wai.neural.int_mlicpp", pin, &[Held::Listing(d.join("model.sha256"))], READABLE_FORMS)
|
|
.map(|_| "ok").unwrap_or_else(|e| e.code());
|
|
let flipped_listing = scratch("flipped-listing");
|
|
let mut l = read("model.sha256");
|
|
l[0] ^= 1;
|
|
std::fs::write(flipped_listing.join("model.sha256"), &l).unwrap();
|
|
std::fs::write(flipped_listing.join("model.wmb"), read("model.wmb")).unwrap();
|
|
assert_eq!(code(&flipped_listing), "prior_not_held");
|
|
let flipped_bundle = scratch("flipped-bundle");
|
|
let mut b = read("model.wmb");
|
|
let at = b.len() - 2;
|
|
b[at] ^= 1;
|
|
std::fs::write(flipped_bundle.join("model.sha256"), read("model.sha256")).unwrap();
|
|
std::fs::write(flipped_bundle.join("model.wmb"), &b).unwrap();
|
|
assert_eq!(code(&flipped_bundle), "part_mismatch");
|
|
let other = scratch("other-name");
|
|
std::fs::write(other.join("other.wmb"), read("model.wmb")).unwrap();
|
|
let other_listing = wai::pin::write_listing(&other, &["other.wmb".to_string()]).unwrap();
|
|
std::fs::write(other.join("model.sha256"), &other_listing).unwrap();
|
|
let mut other_pin = pin.clone();
|
|
other_pin.sha256 = sha256_hex(&other_listing);
|
|
let v = verify("wai.neural.int_mlicpp", &other_pin, &[Held::Listing(other.join("model.sha256"))], READABLE_FORMS)
|
|
.expect("a listing of other.wmb verifies");
|
|
assert_eq!(v.file("model.wmb").unwrap_err().code(), "set_incomplete");
|
|
for d in [flipped_listing, flipped_bundle, other] {
|
|
std::fs::remove_dir_all(d).ok();
|
|
}
|
|
}
|
|
|
|
/// SPEC §7.1: one envelope, the same payload twice, pinned to two parameter sets — a
|
|
/// successor (which names the committed set in `supersedes`) and the committed set.
|
|
/// A sink holding only the committed bundle skips the first entry
|
|
/// (`digest_superseded`), picks the second, and decodes the pinned image.
|
|
#[test]
|
|
fn wai2_pins_select_the_held_model() {
|
|
use wai::codecs::DeclaredTier;
|
|
use wai::container::{Prior, Rendition, Supersedes, WaiMulti};
|
|
use wai::pin::{verify, Held, READABLE_FORMS};
|
|
let pl = envelope().payload;
|
|
let successor = "5ccc".repeat(16);
|
|
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", successor, "int-rans/p16+int-mlicpp/i64")
|
|
.with_tier(DeclaredTier::DecodeEquivalence)
|
|
.with_supersedes(Supersedes::new(MODEL_SHA256))),
|
|
Rendition::media("wai.neural.int_mlicpp", "int_mlicpp", pl)
|
|
.with_prior(Prior::new("committed", MODEL_SHA256, "int-rans/p16+int-mlicpp/i64")
|
|
.with_tier(DeclaredTier::DecodeEquivalence)),
|
|
]);
|
|
let m = WaiMulti::from_bytes(&m.to_bytes().unwrap()).unwrap();
|
|
let held = [Held::File(dir().join("model.wmb"))];
|
|
let p = m.pick(|_| true, |cap, prior| verify(cap, prior, &held, READABLE_FORMS)).expect("a rendition");
|
|
assert_eq!(p.index, 1);
|
|
assert_eq!(p.skipped.iter().map(|(i, s)| (*i, s.code())).collect::<Vec<_>>(), [(0, "digest_superseded")]);
|
|
let bundle = p.verified.as_ref().unwrap().one().unwrap();
|
|
let img = decode(&IntMlicModel::from_bundle(bundle).unwrap(), p.payload).unwrap();
|
|
assert_eq!(hex(fnv1a(&img.rgb)), RGB_FNV1A);
|
|
assert_eq!(m.replicate_refusal(1), None);
|
|
}
|