open-standards/wai/wai-rs/tests/manifest_builders.rs
Transaction Science 22928147f3 wai: the parameter-set pin, component identity, pin-aware selection
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]>
2026-10-01 05:51:50 -04:00

92 lines
5.4 KiB
Rust

//! The manifest types are `#[non_exhaustive]`, so code outside the crate builds
//! them with constructors and builders. These tests run from outside the crate:
//! the builders write exactly the JSON the earlier struct literals wrote, a
//! committed envelope round-trips byte for byte, and members the Rust types model
//! only as `extra` (or a `security` object) survive a parse and a re-serialization.
use wai::container::{
Conditioning, Manifest, ManifestV2, ModelRequirement, Prior, Rendition, RenditionMeta, Security,
Supersedes, Wai, WaiMulti,
};
#[test]
fn builders_write_the_json_a_literal_wrote() {
let m = Manifest::new(
"image",
"replicate",
ModelRequirement::new("wai.neural.bmshj2018").with_fallback("wai.image.png"),
Conditioning::new("bmshj2018").with_prior(Prior::new("q3", "ab".repeat(32), "int-rans")),
)
.with_target(serde_json::json!({"w": 256, "h": 256}));
assert_eq!(
serde_json::to_string(&m).unwrap(),
format!(
r#"{{"wai":"1.0","media":"image","intent":"replicate","model_requirement":{{"capability":"wai.neural.bmshj2018","fallback":"wai.image.png"}},"conditioning":{{"kind":"bmshj2018","prior":{{"id":"q3","sha256":"{}","determinism":"int-rans"}}}},"target":{{"w":256,"h":256}}}}"#,
"ab".repeat(32)
)
);
let plain = Manifest::new("text", "replicate", ModelRequirement::new("wai.text.zstd"), Conditioning::new("zstd"));
assert_eq!(
serde_json::to_string(&plain).unwrap(),
r#"{"wai":"1.0","media":"text","intent":"replicate","model_requirement":{"capability":"wai.text.zstd"},"conditioning":{"kind":"zstd"},"target":null}"#
);
let semantic = ModelRequirement::new("wai.semantic.clip").with_min_sim(0.5).with_sim_metric("dreamsim");
assert_eq!(
serde_json::to_string(&semantic).unwrap(),
r#"{"capability":"wai.semantic.clip","min_sim":0.5,"sim_metric":"dreamsim"}"#
);
let v2 = ManifestV2::new("audio", "replicate", vec![
RenditionMeta::new("wai.neural.encodec32", "encodec_tokens").with_prior(Prior::new("e", "cd".repeat(32), "int")),
RenditionMeta::new("wai.meta.hdr10plus", "descriptor").companion_to("wai.video.av1"),
])
.with_target(serde_json::json!({"sr": 48000}));
assert_eq!(
serde_json::to_string(&v2).unwrap(),
format!(
r#"{{"wai":"1.1","media":"audio","intent":"replicate","renditions":[{{"capability":"wai.neural.encodec32","kind":"encodec_tokens","prior":{{"id":"e","sha256":"{}","determinism":"int"}}}},{{"capability":"wai.meta.hdr10plus","kind":"descriptor","role":"companion","companion_of":"wai.video.av1"}}],"target":{{"sr":48000}}}}"#,
"cd".repeat(32)
)
);
let sup = Prior::new("b", "bb".repeat(32), "int").with_supersedes(Supersedes::new("aa".repeat(32)).with_form("wai.prior.set"));
assert!(serde_json::to_string(&sup).unwrap().ends_with(&format!(
r#""supersedes":{{"sha256":"{}","form":"wai.prior.set"}}}}"#, "aa".repeat(32))));
let with_sec = plain.with_wai("1.1").with_security(Security::new("mls-sframe", 7));
assert!(serde_json::to_string(&with_sec).unwrap().contains(r#""security":{"encryption":"mls-sframe","kid":7}"#));
}
#[test]
fn the_committed_int_mlicpp_envelope_round_trips() {
let d = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_mlicpp");
for name in ["stream.wai", "stream_set.wai"] {
let bytes = std::fs::read(d.join(name)).unwrap();
assert_eq!(Wai::from_bytes(&bytes).unwrap().to_bytes().unwrap(), bytes, "{name}");
}
}
#[test]
fn security_and_unknown_members_survive() {
let v1 = r#"{"wai":"1.0","media":"text","intent":"replicate","model_requirement":{"capability":"wai.text.zstd","later_req":1},"conditioning":{"kind":"zstd","later_cond":[1,2]},"security":{"encryption":"mls-sframe","kid":3,"later_sec":"x"},"target":null,"later_top":{"a":true}}"#;
let mut env = b"WAI1".to_vec();
env.extend_from_slice(&(v1.len() as u32).to_le_bytes());
env.extend_from_slice(v1.as_bytes());
env.extend_from_slice(&2u32.to_le_bytes());
env.extend_from_slice(b"ct");
let w = Wai::from_bytes(&env).unwrap();
assert_eq!(w.manifest.security.as_ref().and_then(|s| s.encryption.as_deref()), Some("mls-sframe"));
assert!(!w.manifest.security.as_ref().unwrap().is_plaintext());
assert_eq!(w.to_bytes().unwrap(), env, "nothing is dropped and the key order is kept");
let v2 = r#"{"wai":"1.1","media":"video","intent":"replicate","renditions":[{"capability":"wai.video.av1","kind":"av1","id":"main","later_entry":{"n":1}}],"target":null,"later_top":2}"#;
let manifest: ManifestV2 = serde_json::from_str(v2).unwrap();
let m = WaiMulti::from_parts(manifest, vec![b"AV1".to_vec()]).unwrap();
let back = WaiMulti::from_bytes(&m.to_bytes().unwrap()).unwrap();
assert_eq!(serde_json::to_string(&back.manifest).unwrap(), v2);
assert_eq!(back.manifest.renditions[0].extra["later_entry"]["n"], 1);
// The payloads must match the entries.
let one: ManifestV2 = serde_json::from_str(v2).unwrap();
assert!(WaiMulti::from_parts(one, vec![]).is_err());
// Rendition builders carry the identity members.
let r = Rendition::media("wai.image.png", "png", b"PNG".to_vec()).with_id("p").with_payload_digest();
assert_eq!(r.meta.id.as_deref(), Some("p"));
assert_eq!(r.meta.sha256.as_deref(), Some(wai::pin::sha256_hex(b"PNG").as_str()));
}