open-standards/wai/wai-rs/tests/ffi_neural_pin.rs
Transaction Science 2060e7de7a neural: the binding check reads the descriptor, the C ABI reports the path, and CI runs the exact decoder
check_reconstruction_binding compared only the hash, so a binding of another
decoderId, format or size than the exact decoder computes was reported as
Differs. reconstruction-binding §2 forbids reporting a binding as failed when
the verifier decoded some other way than its descriptor says. Such a binding
is now BindingCheck::OtherReconstruction, with the path that ran; for the
exact decoder that is any decoderId but 2.

wai_neural_decode_ex dropped the path and the capability, so a native sink
could not state its decoder entry's variant. wai_decoded_t gains `path`
(WAI_DECODE_PATH_SOLE, _EXACT or _FLOAT) and `fallback`, 1 when a WAI1
envelope decoded through its fallback. include/wai.h is regenerated.

The neural gates counted the two JPEG-AI cases' skips as passes, so CI ran
only the one-path case. That step now selects its two committed-input cases by
name, one of them a binding over int_mlicpp checked under its descriptor. A new
job in the 24.04 image fetches the pinned jpegai/v1 bundle and runs the exact
decoder's case with WAI_REQUIRE_SOTA_VECTORS=1, requiring it by name and
refusing a skip. The float decode needs the reference-software assets, which
no job holds, so its case runs locally only; the test header and
determinism-tiers §5 say so. The C ABI step runs the path and fallback test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:29:01 -05:00

150 lines
7.8 KiB
Rust

//! The neural C ABI reports a refused pin as `WAI_ERR_PRIOR` with its reason first
//! in `wai_last_error`, an unsound `replicate` under the strict flag as
//! `WAI_ERR_UNSOUND_REPLICATE`, decodes a `WAI2` envelope through the
//! rendition whose pin the registry holds, and reports the decode path that ran and
//! whether a `WAI1` decoded through its fallback — all on the committed int_mlicpp
//! vector, with no model to fetch.
#![cfg(all(feature = "neural", feature = "ffi"))]
use std::ffi::{CStr, CString};
use std::path::{Path, PathBuf};
use std::ptr;
use wai::codecs::DeclaredTier;
use wai::container::{Prior, Rendition, Supersedes, Wai, WaiMulti};
use wai::ffi::*;
use wai::ffi_neural::*;
fn dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_mlicpp")
}
fn last_error() -> String {
let p = wai_last_error();
if p.is_null() { String::new() } else { unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned() }
}
fn decoded() -> WaiDecoded {
WaiDecoded {
kind: 0, index: 0, data: WaiBuffer { data: ptr::null_mut(), len: 0, cap: 0 },
width: 0, height: 0, sample_rate: 0, n_frames: 0, fps_x_1000: 0, path: 0, fallback: 0,
}
}
fn fnv_of(d: &WaiDecoded) -> String {
let rgb = unsafe { std::slice::from_raw_parts(d.data.data, d.data.len) };
format!("{:016x}", wai::int_hyper_synth::fnv1a(rgb))
}
#[test]
fn tampered_model_is_wai_err_prior() {
let env = std::fs::read(dir().join("stream.wai")).unwrap();
let tampered = std::env::temp_dir().join(format!("wai-ffi-tampered-{}.wmb", std::process::id()));
let mut b = std::fs::read(dir().join("model.wmb")).unwrap();
b[20] ^= 1;
std::fs::write(&tampered, &b).unwrap();
let path = CString::new(tampered.to_str().unwrap()).unwrap();
let mut rgb = WaiBuffer { data: ptr::null_mut(), len: 0, cap: 0 };
let (mut w, mut h) = (0u32, 0u32);
let code = unsafe { wai_neural_decode_image(env.as_ptr(), env.len(), path.as_ptr(), &mut rgb, &mut w, &mut h) };
std::fs::remove_file(&tampered).ok();
assert_eq!(code, WAI_ERR_PRIOR);
assert!(last_error().starts_with("prior_not_held: "), "{}", last_error());
// The true model decodes.
let path = CString::new(dir().join("model.wmb").to_str().unwrap()).unwrap();
assert_eq!(unsafe { wai_neural_decode_image(env.as_ptr(), env.len(), path.as_ptr(), &mut rgb, &mut w, &mut h) }, WAI_OK);
assert_eq!((w, h), (64, 64));
unsafe { wai_buffer_free(rgb) };
}
/// A failed decode leaves `*out` empty, whatever it held before: a caller that
/// frees `out->data` after any status frees nothing it did not get.
#[test]
fn failed_decode_ex_clears_out() {
let env = std::fs::read(dir().join("stream.wai")).unwrap();
let empty = CString::new("{}").unwrap();
let mut out = WaiDecoded {
kind: 7, index: 9, data: WaiBuffer { data: 0x5a5a as *mut u8, len: 5, cap: 5 },
width: 1, height: 1, sample_rate: 1, n_frames: 1, fps_x_1000: 1, path: 3, fallback: 1,
};
let code = unsafe { wai_neural_decode_ex(env.as_ptr(), env.len(), empty.as_ptr(), 0, &mut out) };
assert_ne!(code, WAI_OK);
assert_eq!((out.kind, out.index, out.width, out.height, out.path, out.fallback), (0, 0, 0, 0, 0, 0));
assert!(out.data.data.is_null() && out.data.len == 0 && out.data.cap == 0);
unsafe { wai_buffer_free(out.data) };
// A bad argument after the null check clears it too.
let mut out2 = WaiDecoded { kind: 3, ..decoded() };
let code = unsafe { wai_neural_decode_ex(env.as_ptr(), env.len(), ptr::null(), 0, &mut out2) };
assert_eq!(code, WAI_ERR_INVALID_ARG);
assert_eq!(out2.kind, 0);
}
#[test]
fn strict_replicate_is_wai_err_unsound_replicate() {
let mut env = Wai::from_bytes(&std::fs::read(dir().join("stream.wai")).unwrap()).unwrap();
env.manifest.conditioning.prior = None;
let bytes = env.to_bytes().unwrap();
let reg = CString::new(format!(r#"{{"wai.neural.int_mlicpp":["{}"]}}"#, dir().join("model.wmb").display())).unwrap();
let mut out = decoded();
let code = unsafe { wai_neural_decode_ex(bytes.as_ptr(), bytes.len(), reg.as_ptr(), WAI_DECODE_STRICT, &mut out) };
assert_eq!(code, WAI_ERR_UNSOUND_REPLICATE);
assert!(last_error().contains("replicate_unpinned"), "{}", last_error());
// Without the strict flag the unpinned envelope decodes, through its own capability's
// one decode path, and says so.
assert_eq!(unsafe { wai_neural_decode_ex(bytes.as_ptr(), bytes.len(), reg.as_ptr(), 0, &mut out) }, WAI_OK);
assert_eq!((out.kind, out.width, out.height), (WAI_DECODED_IMAGE, 64, 64));
assert_eq!((out.path, out.fallback), (WAI_DECODE_PATH_SOLE, 0));
unsafe { wai_buffer_free(out.data) };
}
/// `wai_neural_decode_ex` reports the decode path that ran and whether a `WAI1` envelope
/// decoded through its fallback (extensions/determinism-tiers.md §5), which a native sink
/// states in its receipt (extensions/jwp-receipts.md §2.4.12, a decoder entry's `variant`
/// and `path`).
#[test]
fn decode_ex_reports_the_path_and_the_fallback() {
let mut env = Wai::from_bytes(&std::fs::read(dir().join("stream.wai")).unwrap()).unwrap();
env.manifest.conditioning.prior = None;
let reg = CString::new(format!(r#"{{"wai.neural.int_mlicpp":["{}"]}}"#, dir().join("model.wmb").display())).unwrap();
let own = env.to_bytes().unwrap();
let mut out = decoded();
assert_eq!(unsafe { wai_neural_decode_ex(own.as_ptr(), own.len(), reg.as_ptr(), 0, &mut out) }, WAI_OK);
assert_eq!((out.kind, out.path, out.fallback), (WAI_DECODED_IMAGE, WAI_DECODE_PATH_SOLE, 0));
let want = fnv_of(&out);
unsafe { wai_buffer_free(out.data) };
// The same payload under a capability the registry does not hold, with int_mlicpp as
// its fallback: the same picture, through the fallback, and the result says so.
env.manifest.model_requirement.capability = "wai.neural.int_hyper".into();
env.manifest.model_requirement.fallback = Some("wai.neural.int_mlicpp".into());
let via = env.to_bytes().unwrap();
let mut out = decoded();
assert_eq!(unsafe { wai_neural_decode_ex(via.as_ptr(), via.len(), reg.as_ptr(), 0, &mut out) }, WAI_OK, "{}", last_error());
assert_eq!((out.kind, out.path, out.fallback), (WAI_DECODED_IMAGE, WAI_DECODE_PATH_SOLE, 1));
assert_eq!(fnv_of(&out), want);
unsafe { wai_buffer_free(out.data) };
}
#[test]
fn wai2_decode_ex_picks_the_held_pin() {
let pl = Wai::from_bytes(&std::fs::read(dir().join("stream.wai")).unwrap()).unwrap().payload;
let committed = wai::pin::sha256_hex(&std::fs::read(dir().join("model.wmb")).unwrap());
let det = "int-rans/p16+int-mlicpp/i64";
let env = 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)),
]).to_bytes().unwrap();
let reg = CString::new(format!(r#"{{"wai.neural.int_mlicpp":["{}"]}}"#, dir().join("model.wmb").display())).unwrap();
let mut out = decoded();
let code = unsafe { wai_neural_decode_ex(env.as_ptr(), env.len(), reg.as_ptr(), WAI_DECODE_STRICT, &mut out) };
assert_eq!(code, WAI_OK, "{}", last_error());
assert_eq!((out.index, out.kind), (1, WAI_DECODED_IMAGE));
assert_eq!(fnv_of(&out), "7c0b9a4b3423d775");
unsafe { wai_buffer_free(out.data) };
// With no set held, nothing is picked.
let empty = CString::new("{}").unwrap();
let mut out = decoded();
assert_eq!(unsafe { wai_neural_decode_ex(env.as_ptr(), env.len(), empty.as_ptr(), 0, &mut out) }, WAI_ERR_NO_RENDITION);
}