SPEC §3.1 defines the pin a manifest carries (conditioning.prior, and a WAI2 rendition's prior): id, sha256, determinism, and the optional form, tier and supersedes. The digest covers the whole parameter set. A set of several files is pinned through its listing, the new registered prior form wai.prior.set. A sink verifies the pin before it decodes, reports a refusal with one of an ordered set of snake_case reasons, and decodes only from the set it verified. Tier is a closed vocabulary; supersedes is informative. A pin that does not resolve makes its capability unsupported for that envelope: SPEC §4 dispatch continues at the fallback, §7.1 selection at the next rendition, and an inert envelope's error names the reasons. A rendition entry may carry id, sha256 and, on a companion, companion_of_id. SPEC §7 gains a Digests subsection, and §8 says how a MINOR revision adds an optional member. In wai-rs: the manifest types are #[non_exhaustive] with builders; a new pin module verifies pins in memory and on disk; Wai::dispatch_verified, WaiMulti::pick and primary check pins and component digests; every neural decoder reads through ParamSource; a codec-free `ffi` feature carries the C envelope surface with wai_envelope_select and wai_neural_decode_ex. wai-web wraps wai::container. New vectors: the int_mlicpp set-form twin and the pin-conformance corpus with an independent Python verifier, plus sha256.json sidecars for the vectors of capabilities that decode against a parameter set. Squashed from wai/stream-a-parameter-set-identity onto link 12. Conflicts are resolved to keep both sides; the integration commits that follow make the two streams agree. Co-Authored-By: Claude Opus 5.5 <[email protected]>
54 lines
2.4 KiB
Rust
54 lines
2.4 KiB
Rust
//! `wai.generate.spectral` — **generation-equivalence**: a deterministic integer
|
|
//! generator produces byte-identical bytes on every machine from the same
|
|
//! `(model, prompt, seed, steps)`, so its hash is portable (the create-side of
|
|
//! "conformance is a hash"). Committed vector under `tests/vectors/generate/`;
|
|
//! never skips.
|
|
#![cfg(feature = "generate")]
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use wai::generate::{fnv1a, generate, SpectralModel};
|
|
|
|
#[path = "support/sha256_sidecar.rs"]
|
|
mod sha256_sidecar;
|
|
|
|
fn dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/generate")
|
|
}
|
|
fn jload(name: &str) -> serde_json::Value {
|
|
serde_json::from_slice(&std::fs::read(dir().join(name)).unwrap()).unwrap()
|
|
}
|
|
fn hash(rgb: &[u8]) -> String {
|
|
format!("{:016x}", fnv1a(rgb))
|
|
}
|
|
|
|
#[test]
|
|
fn generation_is_deterministic_and_pinned() {
|
|
let m = SpectralModel::from_value(&jload("generate_spectral_model.json"));
|
|
let pin = jload("generate.json");
|
|
let (prompt, seed, steps, h, w) = (
|
|
pin["prompt"].as_str().unwrap(),
|
|
pin["seed"].as_u64().unwrap(),
|
|
pin["steps"].as_u64().unwrap() as usize,
|
|
pin["h"].as_u64().unwrap() as usize,
|
|
pin["w"].as_u64().unwrap() as usize,
|
|
);
|
|
|
|
// 1. PIN: the committed (prompt, seed, steps) reproduces the committed hash,
|
|
// byte-for-byte — the portable receipt of a generated image.
|
|
let rgb = generate(&m, prompt, seed, steps, h, w);
|
|
assert_eq!(rgb.len(), h * w * 3);
|
|
let got = hash(&rgb);
|
|
println!("generate.spectral: {h}x{w} '{prompt}' seed={seed} steps={steps} -> fnv1a {got}");
|
|
assert_eq!(got, pin["rgb_hash"].as_str().unwrap(), "pinned generation must reproduce the committed hash");
|
|
sha256_sidecar::one(&dir(), "generate", "RGB8: rows top to bottom, pixels left to right, R, G, B bytes", &rgb);
|
|
|
|
// 2. DETERMINISM: regenerating the same inputs is byte-identical.
|
|
assert_eq!(generate(&m, prompt, seed, steps, h, w), rgb, "regeneration must be byte-identical");
|
|
|
|
// 3. SENSITIVITY: a different prompt or seed draws a different image (the
|
|
// generation is conditioned, not constant).
|
|
assert_ne!(hash(&generate(&m, "a different prompt", seed, steps, h, w)), got, "prompt must change the output");
|
|
assert_ne!(hash(&generate(&m, prompt, seed ^ 1, steps, h, w)), got, "seed must change the output");
|
|
assert_ne!(hash(&generate(&m, prompt, seed, steps + 1, h, w)), got, "steps must change the output");
|
|
}
|