Fix round, item 2. Policy::parse_bytes reads a policy as bytes; the CLI and the corpus use it (no lossy decode). Before the value kinds, and before anything else, these are policy-toml in both implementations: - bytes that are not UTF-8; - a byte-order mark (the house rule: UTF-8 with no byte-order mark); - not TOML 1.0, an integer outside TOML's 64-bit range among it (pack_verify.py's reader takes any size, so it now checks the range); - arrays and tables nested past 32, the document's table at depth 1: a pass of its own before the value-kind walk, so a nesting fault outranks a float found earlier in key order, as the Rust parser's own nesting limit already made it. pack_verify.py also reads a RecursionError from its TOML reader as policy-toml. packaging §3.1 and §3.2 state it. Corpus: a byte-order mark, bytes that are not UTF-8, an integer at 2^63, nesting to depth 33 and 80. policy_diff.py (from the review's byte-level cases and seeded policies) compares every verdict and digest of wai-pack policy-verdict with pack_verify.py's; CI runs it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
113 lines
5 KiB
Rust
113 lines
5 KiB
Rust
//! What a packager may emit ([packaging] §3.6): only capabilities SPEC §5 registers as
|
|
//! payloads with a pinned format, each in a role it can play, and, under
|
|
//! `royalty_free_only`, only those the registry classifies royalty-free. Read from
|
|
//! the registry of the `wai` crate (`codecs`), never from the policy.
|
|
//!
|
|
//! For each rung in ladder order, the first that holds is its refusal:
|
|
//!
|
|
//! 1. `capability-unknown:<cap>`: SPEC §5 does not register the string.
|
|
//! 2. `capability-not-payload:<cap>`: it is registered as a record or a derivation
|
|
//! operation, which names no payload format.
|
|
//! 3. `capability-candidate:<cap>`: a candidate, whose payload format no revision has
|
|
//! pinned (SPEC §5, *Candidate capabilities*); an encoder MUST NOT emit it.
|
|
//! 4. `capability-role:<cap>`: a base or a floor whose capability is not standalone
|
|
//! media, or a refinement whose capability is not refinement-only.
|
|
//! 5. Under `royalty_free_only`: `license-unclassified:<cap>` where the registry gives
|
|
//! no licence class, and `license-refused:<cap>:<label>` for any class other than
|
|
//! royalty-free, with the class's SPEC §5 label.
|
|
//!
|
|
//! [packaging]: ../../../extensions/packaging.md
|
|
|
|
use crate::policy::{Policy, PolicyError, Role};
|
|
use wai::codecs::{self, Kind, LicenseClass};
|
|
|
|
/// One rung's refusal: the rung's id and the reason, which names the capability.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct CapRefusal {
|
|
pub rung: String,
|
|
pub code: String,
|
|
}
|
|
|
|
/// The refusal of capability `cap` in role `role`, or `None` when a packager may emit it.
|
|
pub fn rung_refusal(cap: &str, role: Role, royalty_free_only: bool) -> Option<String> {
|
|
match codecs::capability_kind(cap) {
|
|
None => return Some(format!("capability-unknown:{cap}")),
|
|
Some(Kind::Record | Kind::Derivation) => return Some(format!("capability-not-payload:{cap}")),
|
|
Some(Kind::Payload | Kind::Companion | Kind::Refinement) => {}
|
|
}
|
|
if codecs::is_candidate(cap) {
|
|
return Some(format!("capability-candidate:{cap}"));
|
|
}
|
|
let want = match role {
|
|
Role::Base | Role::Floor => "standalone",
|
|
Role::Refinement => "refinement",
|
|
};
|
|
if codecs::capability_role(cap) != Some(want) {
|
|
return Some(format!("capability-role:{cap}"));
|
|
}
|
|
if royalty_free_only {
|
|
match codecs::capability_license(cap) {
|
|
None => return Some(format!("license-unclassified:{cap}")),
|
|
Some(LicenseClass::RoyaltyFree) => {}
|
|
Some(c) => return Some(format!("license-refused:{cap}:{}", c.label())),
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Every rung's refusal, in ladder order; `Ok` when there is none.
|
|
pub fn check(p: &Policy) -> Result<(), Vec<CapRefusal>> {
|
|
let r: Vec<CapRefusal> = p
|
|
.ladder
|
|
.iter()
|
|
.filter_map(|x| rung_refusal(&x.capability, x.role, p.royalty_free_only).map(|code| CapRefusal { rung: x.id.clone(), code }))
|
|
.collect();
|
|
if r.is_empty() { Ok(()) } else { Err(r) }
|
|
}
|
|
|
|
/// Why a packager refuses a policy: the policy itself (§3), or what it asks to emit
|
|
/// (§3.6).
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum Refused {
|
|
Policy(PolicyError),
|
|
Capability(Vec<CapRefusal>),
|
|
}
|
|
|
|
impl Refused {
|
|
/// The reason a corpus states: the policy's, or the first rung's.
|
|
pub fn code(&self) -> String {
|
|
match self {
|
|
Refused::Policy(e) => e.code().to_string(),
|
|
Refused::Capability(v) => v[0].code.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A policy a packager accepts, from its bytes: parsed, checked, with the overrides
|
|
/// applied, and every rung emittable.
|
|
pub fn admit(bytes: &[u8], o: &crate::policy::Overrides) -> Result<Policy, Refused> {
|
|
let p = Policy::parse_bytes(bytes).map_err(Refused::Policy)?.effective(o);
|
|
check(&p).map_err(Refused::Capability)?;
|
|
Ok(p)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn each_refusal_in_its_order() {
|
|
let r = |cap, role, rf| rung_refusal(cap, role, rf);
|
|
assert_eq!(r("wai.video.int_motion", Role::Base, true), None);
|
|
assert_eq!(r("wai.video.int_refine", Role::Refinement, true), None);
|
|
assert_eq!(r("wai.video.av1", Role::Floor, true), None);
|
|
assert_eq!(r("wai.video.nope", Role::Base, false).unwrap(), "capability-unknown:wai.video.nope");
|
|
assert_eq!(r("wai.video.package", Role::Base, false).unwrap(), "capability-not-payload:wai.video.package");
|
|
assert_eq!(r("wai.splat.sog", Role::Base, false).unwrap(), "capability-candidate:wai.splat.sog");
|
|
assert_eq!(r("wai.video.int_refine", Role::Base, false).unwrap(), "capability-role:wai.video.int_refine");
|
|
assert_eq!(r("wai.video.int_motion", Role::Refinement, false).unwrap(), "capability-role:wai.video.int_motion");
|
|
assert_eq!(r("wai.meta.hdr10plus", Role::Base, false).unwrap(), "capability-role:wai.meta.hdr10plus");
|
|
assert_eq!(r("wai.video.avc", Role::Base, true).unwrap(), "license-refused:wai.video.avc:patent-pool");
|
|
assert_eq!(r("wai.video.avc", Role::Base, false), None);
|
|
}
|
|
}
|